// https://github.com/CedricGuillemet/ImGuizmo
// v 1.89 WIP
//
// The MIT License(MIT)
//
// Copyright(c) 2021 Cedric Guillemet
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files(the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions :
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//

#ifndef IMGUI_DEFINE_MATH_OPERATORS
	#define IMGUI_DEFINE_MATH_OPERATORS
#endif
#include "imgui.h"
#include "imgui_internal.h"
#include "ImGuizmo.h"

#if defined(_MSC_VER) || defined(__MINGW32__)
	#include <malloc.h>
#endif
#if !defined(_MSC_VER) && !defined(__MINGW64_VERSION_MAJOR)
	#define _malloca(x) alloca(x)
	#define _freea(x)
#endif

// includes patches for multiview from
// https://github.com/CedricGuillemet/ImGuizmo/issues/15

namespace IMGUIZMO_NAMESPACE
{
static const float ZPI = 3.14159265358979323846f;
static const float RAD2DEG = ( 180.f / ZPI );
static const float DEG2RAD = ( ZPI / 180.f );
const float screenRotateSize = 0.06f;
// scale a bit so translate axis do not touch when in universal
const float rotationDisplayFactor = 1.2f;

static OPERATION operator&( OPERATION lhs, OPERATION rhs )
{
	return static_cast<OPERATION>( static_cast<int>( lhs ) & static_cast<int>( rhs ) );
}

static bool operator!=( OPERATION lhs, int rhs )
{
	return static_cast<int>( lhs ) != rhs;
}

static bool Intersects( OPERATION lhs, OPERATION rhs )
{
	return ( lhs & rhs ) != 0;
}

// True if lhs contains rhs
static bool Contains( OPERATION lhs, OPERATION rhs )
{
	return ( lhs & rhs ) == rhs;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// utility and math

void FPU_MatrixF_x_MatrixF( const float* a, const float* b, float* r )
{
	r[0] = a[0] * b[0] + a[1] * b[4] + a[2] * b[8] + a[3] * b[12];
	r[1] = a[0] * b[1] + a[1] * b[5] + a[2] * b[9] + a[3] * b[13];
	r[2] = a[0] * b[2] + a[1] * b[6] + a[2] * b[10] + a[3] * b[14];
	r[3] = a[0] * b[3] + a[1] * b[7] + a[2] * b[11] + a[3] * b[15];

	r[4] = a[4] * b[0] + a[5] * b[4] + a[6] * b[8] + a[7] * b[12];
	r[5] = a[4] * b[1] + a[5] * b[5] + a[6] * b[9] + a[7] * b[13];
	r[6] = a[4] * b[2] + a[5] * b[6] + a[6] * b[10] + a[7] * b[14];
	r[7] = a[4] * b[3] + a[5] * b[7] + a[6] * b[11] + a[7] * b[15];

	r[8] = a[8] * b[0] + a[9] * b[4] + a[10] * b[8] + a[11] * b[12];
	r[9] = a[8] * b[1] + a[9] * b[5] + a[10] * b[9] + a[11] * b[13];
	r[10] = a[8] * b[2] + a[9] * b[6] + a[10] * b[10] + a[11] * b[14];
	r[11] = a[8] * b[3] + a[9] * b[7] + a[10] * b[11] + a[11] * b[15];

	r[12] = a[12] * b[0] + a[13] * b[4] + a[14] * b[8] + a[15] * b[12];
	r[13] = a[12] * b[1] + a[13] * b[5] + a[14] * b[9] + a[15] * b[13];
	r[14] = a[12] * b[2] + a[13] * b[6] + a[14] * b[10] + a[15] * b[14];
	r[15] = a[12] * b[3] + a[13] * b[7] + a[14] * b[11] + a[15] * b[15];
}

void Frustum( float left, float right, float bottom, float top, float znear, float zfar, float* m16 )
{
	float temp, temp2, temp3, temp4;
	temp = 2.0f * znear;
	temp2 = right - left;
	temp3 = top - bottom;
	temp4 = zfar - znear;
	m16[0] = temp / temp2;
	m16[1] = 0.0;
	m16[2] = 0.0;
	m16[3] = 0.0;
	m16[4] = 0.0;
	m16[5] = temp / temp3;
	m16[6] = 0.0;
	m16[7] = 0.0;
	m16[8] = ( right + left ) / temp2;
	m16[9] = ( top + bottom ) / temp3;
	m16[10] = ( -zfar - znear ) / temp4;
	m16[11] = -1.0f;
	m16[12] = 0.0;
	m16[13] = 0.0;
	m16[14] = ( -temp * zfar ) / temp4;
	m16[15] = 0.0;
}

void Perspective( float fovyInDegrees, float aspectRatio, float znear, float zfar, float* m16 )
{
	float ymax, xmax;
	ymax = znear * tanf( fovyInDegrees * DEG2RAD );
	xmax = ymax * aspectRatio;
	Frustum( -xmax, xmax, -ymax, ymax, znear, zfar, m16 );
}

void Cross( const float* a, const float* b, float* r )
{
	r[0] = a[1] * b[2] - a[2] * b[1];
	r[1] = a[2] * b[0] - a[0] * b[2];
	r[2] = a[0] * b[1] - a[1] * b[0];
}

float Dot( const float* a, const float* b )
{
	return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
}

void Normalize( const float* a, float* r )
{
	float il = 1.f / ( sqrtf( Dot( a, a ) ) + FLT_EPSILON );
	r[0] = a[0] * il;
	r[1] = a[1] * il;
	r[2] = a[2] * il;
}

void LookAt( const float* eye, const float* at, const float* up, float* m16 )
{
	float X[3], Y[3], Z[3], tmp[3];

	tmp[0] = eye[0] - at[0];
	tmp[1] = eye[1] - at[1];
	tmp[2] = eye[2] - at[2];
	Normalize( tmp, Z );
	Normalize( up, Y );
	Cross( Y, Z, tmp );
	Normalize( tmp, X );
	Cross( Z, X, tmp );
	Normalize( tmp, Y );

	m16[0] = X[0];
	m16[1] = Y[0];
	m16[2] = Z[0];
	m16[3] = 0.0f;
	m16[4] = X[1];
	m16[5] = Y[1];
	m16[6] = Z[1];
	m16[7] = 0.0f;
	m16[8] = X[2];
	m16[9] = Y[2];
	m16[10] = Z[2];
	m16[11] = 0.0f;
	m16[12] = -Dot( X, eye );
	m16[13] = -Dot( Y, eye );
	m16[14] = -Dot( Z, eye );
	m16[15] = 1.0f;
}

template <typename T> T Clamp( T x, T y, T z )
{
	return ( ( x < y ) ? y : ( ( x > z ) ? z : x ) );
}
template <typename T> T max( T x, T y )
{
	return ( x > y ) ? x : y;
}
template <typename T> T min( T x, T y )
{
	return ( x < y ) ? x : y;
}
template <typename T> bool IsWithin( T x, T y, T z )
{
	return ( x >= y ) && ( x <= z );
}

struct matrix_t;
struct vec_t
{
public:
	float x, y, z, w;

	void Lerp( const vec_t& v, float t )
	{
		x += ( v.x - x ) * t;
		y += ( v.y - y ) * t;
		z += ( v.z - z ) * t;
		w += ( v.w - w ) * t;
	}

	void Set( float v )
	{
		x = y = z = w = v;
	}
	void Set( float _x, float _y, float _z = 0.f, float _w = 0.f )
	{
		x = _x;
		y = _y;
		z = _z;
		w = _w;
	}

	vec_t& operator -= ( const vec_t& v )
	{
		x -= v.x;
		y -= v.y;
		z -= v.z;
		w -= v.w;
		return *this;
	}
	vec_t& operator += ( const vec_t& v )
	{
		x += v.x;
		y += v.y;
		z += v.z;
		w += v.w;
		return *this;
	}
	vec_t& operator *= ( const vec_t& v )
	{
		x *= v.x;
		y *= v.y;
		z *= v.z;
		w *= v.w;
		return *this;
	}
	vec_t& operator *= ( float v )
	{
		x *= v;
		y *= v;
		z *= v;
		w *= v;
		return *this;
	}

	vec_t operator * ( float f ) const;
	vec_t operator - () const;
	vec_t operator - ( const vec_t& v ) const;
	vec_t operator + ( const vec_t& v ) const;
	vec_t operator * ( const vec_t& v ) const;

	const vec_t& operator + () const
	{
		return ( *this );
	}
	float Length() const
	{
		return sqrtf( x * x + y * y + z * z );
	};
	float LengthSq() const
	{
		return ( x * x + y * y + z * z );
	};
	vec_t Normalize()
	{
		( *this ) *= ( 1.f / ( Length() > FLT_EPSILON ? Length() : FLT_EPSILON ) );
		return ( *this );
	}
	vec_t Normalize( const vec_t& v )
	{
		this->Set( v.x, v.y, v.z, v.w );
		this->Normalize();
		return ( *this );
	}
	vec_t Abs() const;

	void Cross( const vec_t& v )
	{
		vec_t res;
		res.x = y * v.z - z * v.y;
		res.y = z * v.x - x * v.z;
		res.z = x * v.y - y * v.x;

		x = res.x;
		y = res.y;
		z = res.z;
		w = 0.f;
	}

	void Cross( const vec_t& v1, const vec_t& v2 )
	{
		x = v1.y * v2.z - v1.z * v2.y;
		y = v1.z * v2.x - v1.x * v2.z;
		z = v1.x * v2.y - v1.y * v2.x;
		w = 0.f;
	}

	float Dot( const vec_t& v ) const
	{
		return ( x * v.x ) + ( y * v.y ) + ( z * v.z ) + ( w * v.w );
	}

	float Dot3( const vec_t& v ) const
	{
		return ( x * v.x ) + ( y * v.y ) + ( z * v.z );
	}

	void Transform( const matrix_t& matrix );
	void Transform( const vec_t& s, const matrix_t& matrix );

	void TransformVector( const matrix_t& matrix );
	void TransformPoint( const matrix_t& matrix );
	void TransformVector( const vec_t& v, const matrix_t& matrix )
	{
		( *this ) = v;
		this->TransformVector( matrix );
	}
	void TransformPoint( const vec_t& v, const matrix_t& matrix )
	{
		( *this ) = v;
		this->TransformPoint( matrix );
	}

	float& operator []( size_t index )
	{
		return ( ( float* )&x )[index];
	}
	const float& operator []( size_t index ) const
	{
		return ( ( float* )&x )[index];
	}
	bool operator!=( const vec_t& other ) const
	{
		return memcmp( this, &other, sizeof( vec_t ) ) != 0;
	}
};

vec_t makeVect( float _x, float _y, float _z = 0.f, float _w = 0.f )
{
	vec_t res;
	res.x = _x;
	res.y = _y;
	res.z = _z;
	res.w = _w;
	return res;
}
vec_t makeVect( ImVec2 v )
{
	vec_t res;
	res.x = v.x;
	res.y = v.y;
	res.z = 0.f;
	res.w = 0.f;
	return res;
}
vec_t vec_t::operator * ( float f ) const
{
	return makeVect( x * f, y * f, z * f, w * f );
}
vec_t vec_t::operator - () const
{
	return makeVect( -x, -y, -z, -w );
}
vec_t vec_t::operator - ( const vec_t& v ) const
{
	return makeVect( x - v.x, y - v.y, z - v.z, w - v.w );
}
vec_t vec_t::operator + ( const vec_t& v ) const
{
	return makeVect( x + v.x, y + v.y, z + v.z, w + v.w );
}
vec_t vec_t::operator * ( const vec_t& v ) const
{
	return makeVect( x * v.x, y * v.y, z * v.z, w * v.w );
}
vec_t vec_t::Abs() const
{
	return makeVect( fabsf( x ), fabsf( y ), fabsf( z ) );
}

vec_t Normalized( const vec_t& v )
{
	vec_t res;
	res = v;
	res.Normalize();
	return res;
}
vec_t Cross( const vec_t& v1, const vec_t& v2 )
{
	vec_t res;
	res.x = v1.y * v2.z - v1.z * v2.y;
	res.y = v1.z * v2.x - v1.x * v2.z;
	res.z = v1.x * v2.y - v1.y * v2.x;
	res.w = 0.f;
	return res;
}

float Dot( const vec_t& v1, const vec_t& v2 )
{
	return ( v1.x * v2.x ) + ( v1.y * v2.y ) + ( v1.z * v2.z );
}

vec_t BuildPlan( const vec_t& p_point1, const vec_t& p_normal )
{
	vec_t normal, res;
	normal.Normalize( p_normal );
	res.w = normal.Dot( p_point1 );
	res.x = normal.x;
	res.y = normal.y;
	res.z = normal.z;
	return res;
}

struct matrix_t
{
public:

	union
	{
		float m[4][4];
		float m16[16];
		struct
		{
			vec_t right, up, dir, position;
		} v;
		vec_t component[4];
	};

	operator float* ()
	{
		return m16;
	}
	operator const float* () const
	{
		return m16;
	}
	void Translation( float _x, float _y, float _z )
	{
		this->Translation( makeVect( _x, _y, _z ) );
	}

	void Translation( const vec_t& vt )
	{
		v.right.Set( 1.f, 0.f, 0.f, 0.f );
		v.up.Set( 0.f, 1.f, 0.f, 0.f );
		v.dir.Set( 0.f, 0.f, 1.f, 0.f );
		v.position.Set( vt.x, vt.y, vt.z, 1.f );
	}

	void Scale( float _x, float _y, float _z )
	{
		v.right.Set( _x, 0.f, 0.f, 0.f );
		v.up.Set( 0.f, _y, 0.f, 0.f );
		v.dir.Set( 0.f, 0.f, _z, 0.f );
		v.position.Set( 0.f, 0.f, 0.f, 1.f );
	}
	void Scale( const vec_t& s )
	{
		Scale( s.x, s.y, s.z );
	}

	matrix_t& operator *= ( const matrix_t& mat )
	{
		matrix_t tmpMat;
		tmpMat = *this;
		tmpMat.Multiply( mat );
		*this = tmpMat;
		return *this;
	}
	matrix_t operator * ( const matrix_t& mat ) const
	{
		matrix_t matT;
		matT.Multiply( *this, mat );
		return matT;
	}

	void Multiply( const matrix_t& matrix )
	{
		matrix_t tmp;
		tmp = *this;

		FPU_MatrixF_x_MatrixF( ( float* )&tmp, ( float* )&matrix, ( float* )this );
	}

	void Multiply( const matrix_t& m1, const matrix_t& m2 )
	{
		FPU_MatrixF_x_MatrixF( ( float* )&m1, ( float* )&m2, ( float* )this );
	}

	float GetDeterminant() const
	{
		return m[0][0] * m[1][1] * m[2][2] + m[0][1] * m[1][2] * m[2][0] + m[0][2] * m[1][0] * m[2][1] -
			   m[0][2] * m[1][1] * m[2][0] - m[0][1] * m[1][0] * m[2][2] - m[0][0] * m[1][2] * m[2][1];
	}

	float Inverse( const matrix_t& srcMatrix, bool affine = false );
	void SetToIdentity()
	{
		v.right.Set( 1.f, 0.f, 0.f, 0.f );
		v.up.Set( 0.f, 1.f, 0.f, 0.f );
		v.dir.Set( 0.f, 0.f, 1.f, 0.f );
		v.position.Set( 0.f, 0.f, 0.f, 1.f );
	}
	void Transpose()
	{
		matrix_t tmpm;
		for( int l = 0; l < 4; l++ )
		{
			for( int c = 0; c < 4; c++ )
			{
				tmpm.m[l][c] = m[c][l];
			}
		}
		( *this ) = tmpm;
	}

	void RotationAxis( const vec_t& axis, float angle );

	void OrthoNormalize()
	{
		v.right.Normalize();
		v.up.Normalize();
		v.dir.Normalize();
	}
};

void vec_t::Transform( const matrix_t& matrix )
{
	vec_t out;

	out.x = x * matrix.m[0][0] + y * matrix.m[1][0] + z * matrix.m[2][0] + w * matrix.m[3][0];
	out.y = x * matrix.m[0][1] + y * matrix.m[1][1] + z * matrix.m[2][1] + w * matrix.m[3][1];
	out.z = x * matrix.m[0][2] + y * matrix.m[1][2] + z * matrix.m[2][2] + w * matrix.m[3][2];
	out.w = x * matrix.m[0][3] + y * matrix.m[1][3] + z * matrix.m[2][3] + w * matrix.m[3][3];

	x = out.x;
	y = out.y;
	z = out.z;
	w = out.w;
}

void vec_t::Transform( const vec_t& s, const matrix_t& matrix )
{
	*this = s;
	Transform( matrix );
}

void vec_t::TransformPoint( const matrix_t& matrix )
{
	vec_t out;

	out.x = x * matrix.m[0][0] + y * matrix.m[1][0] + z * matrix.m[2][0] + matrix.m[3][0];
	out.y = x * matrix.m[0][1] + y * matrix.m[1][1] + z * matrix.m[2][1] + matrix.m[3][1];
	out.z = x * matrix.m[0][2] + y * matrix.m[1][2] + z * matrix.m[2][2] + matrix.m[3][2];
	out.w = x * matrix.m[0][3] + y * matrix.m[1][3] + z * matrix.m[2][3] + matrix.m[3][3];

	x = out.x;
	y = out.y;
	z = out.z;
	w = out.w;
}

void vec_t::TransformVector( const matrix_t& matrix )
{
	vec_t out;

	out.x = x * matrix.m[0][0] + y * matrix.m[1][0] + z * matrix.m[2][0];
	out.y = x * matrix.m[0][1] + y * matrix.m[1][1] + z * matrix.m[2][1];
	out.z = x * matrix.m[0][2] + y * matrix.m[1][2] + z * matrix.m[2][2];
	out.w = x * matrix.m[0][3] + y * matrix.m[1][3] + z * matrix.m[2][3];

	x = out.x;
	y = out.y;
	z = out.z;
	w = out.w;
}

float matrix_t::Inverse( const matrix_t& srcMatrix, bool affine )
{
	float det = 0;

	if( affine )
	{
		det = GetDeterminant();
		float s = 1 / det;
		m[0][0] = ( srcMatrix.m[1][1] * srcMatrix.m[2][2] - srcMatrix.m[1][2] * srcMatrix.m[2][1] ) * s;
		m[0][1] = ( srcMatrix.m[2][1] * srcMatrix.m[0][2] - srcMatrix.m[2][2] * srcMatrix.m[0][1] ) * s;
		m[0][2] = ( srcMatrix.m[0][1] * srcMatrix.m[1][2] - srcMatrix.m[0][2] * srcMatrix.m[1][1] ) * s;
		m[1][0] = ( srcMatrix.m[1][2] * srcMatrix.m[2][0] - srcMatrix.m[1][0] * srcMatrix.m[2][2] ) * s;
		m[1][1] = ( srcMatrix.m[2][2] * srcMatrix.m[0][0] - srcMatrix.m[2][0] * srcMatrix.m[0][2] ) * s;
		m[1][2] = ( srcMatrix.m[0][2] * srcMatrix.m[1][0] - srcMatrix.m[0][0] * srcMatrix.m[1][2] ) * s;
		m[2][0] = ( srcMatrix.m[1][0] * srcMatrix.m[2][1] - srcMatrix.m[1][1] * srcMatrix.m[2][0] ) * s;
		m[2][1] = ( srcMatrix.m[2][0] * srcMatrix.m[0][1] - srcMatrix.m[2][1] * srcMatrix.m[0][0] ) * s;
		m[2][2] = ( srcMatrix.m[0][0] * srcMatrix.m[1][1] - srcMatrix.m[0][1] * srcMatrix.m[1][0] ) * s;
		m[3][0] = -( m[0][0] * srcMatrix.m[3][0] + m[1][0] * srcMatrix.m[3][1] + m[2][0] * srcMatrix.m[3][2] );
		m[3][1] = -( m[0][1] * srcMatrix.m[3][0] + m[1][1] * srcMatrix.m[3][1] + m[2][1] * srcMatrix.m[3][2] );
		m[3][2] = -( m[0][2] * srcMatrix.m[3][0] + m[1][2] * srcMatrix.m[3][1] + m[2][2] * srcMatrix.m[3][2] );
	}
	else
	{
		// transpose matrix
		float src[16];
		for( int i = 0; i < 4; ++i )
		{
			src[i] = srcMatrix.m16[i * 4];
			src[i + 4] = srcMatrix.m16[i * 4 + 1];
			src[i + 8] = srcMatrix.m16[i * 4 + 2];
			src[i + 12] = srcMatrix.m16[i * 4 + 3];
		}

		// calculate pairs for first 8 elements (cofactors)
		float tmp[12]; // temp array for pairs
		tmp[0] = src[10] * src[15];
		tmp[1] = src[11] * src[14];
		tmp[2] = src[9] * src[15];
		tmp[3] = src[11] * src[13];
		tmp[4] = src[9] * src[14];
		tmp[5] = src[10] * src[13];
		tmp[6] = src[8] * src[15];
		tmp[7] = src[11] * src[12];
		tmp[8] = src[8] * src[14];
		tmp[9] = src[10] * src[12];
		tmp[10] = src[8] * src[13];
		tmp[11] = src[9] * src[12];

		// calculate first 8 elements (cofactors)
		m16[0] = ( tmp[0] * src[5] + tmp[3] * src[6] + tmp[4] * src[7] ) - ( tmp[1] * src[5] + tmp[2] * src[6] + tmp[5] * src[7] );
		m16[1] = ( tmp[1] * src[4] + tmp[6] * src[6] + tmp[9] * src[7] ) - ( tmp[0] * src[4] + tmp[7] * src[6] + tmp[8] * src[7] );
		m16[2] = ( tmp[2] * src[4] + tmp[7] * src[5] + tmp[10] * src[7] ) - ( tmp[3] * src[4] + tmp[6] * src[5] + tmp[11] * src[7] );
		m16[3] = ( tmp[5] * src[4] + tmp[8] * src[5] + tmp[11] * src[6] ) - ( tmp[4] * src[4] + tmp[9] * src[5] + tmp[10] * src[6] );
		m16[4] = ( tmp[1] * src[1] + tmp[2] * src[2] + tmp[5] * src[3] ) - ( tmp[0] * src[1] + tmp[3] * src[2] + tmp[4] * src[3] );
		m16[5] = ( tmp[0] * src[0] + tmp[7] * src[2] + tmp[8] * src[3] ) - ( tmp[1] * src[0] + tmp[6] * src[2] + tmp[9] * src[3] );
		m16[6] = ( tmp[3] * src[0] + tmp[6] * src[1] + tmp[11] * src[3] ) - ( tmp[2] * src[0] + tmp[7] * src[1] + tmp[10] * src[3] );
		m16[7] = ( tmp[4] * src[0] + tmp[9] * src[1] + tmp[10] * src[2] ) - ( tmp[5] * src[0] + tmp[8] * src[1] + tmp[11] * src[2] );

		// calculate pairs for second 8 elements (cofactors)
		tmp[0] = src[2] * src[7];
		tmp[1] = src[3] * src[6];
		tmp[2] = src[1] * src[7];
		tmp[3] = src[3] * src[5];
		tmp[4] = src[1] * src[6];
		tmp[5] = src[2] * src[5];
		tmp[6] = src[0] * src[7];
		tmp[7] = src[3] * src[4];
		tmp[8] = src[0] * src[6];
		tmp[9] = src[2] * src[4];
		tmp[10] = src[0] * src[5];
		tmp[11] = src[1] * src[4];

		// calculate second 8 elements (cofactors)
		m16[8] = ( tmp[0] * src[13] + tmp[3] * src[14] + tmp[4] * src[15] ) - ( tmp[1] * src[13] + tmp[2] * src[14] + tmp[5] * src[15] );
		m16[9] = ( tmp[1] * src[12] + tmp[6] * src[14] + tmp[9] * src[15] ) - ( tmp[0] * src[12] + tmp[7] * src[14] + tmp[8] * src[15] );
		m16[10] = ( tmp[2] * src[12] + tmp[7] * src[13] + tmp[10] * src[15] ) - ( tmp[3] * src[12] + tmp[6] * src[13] + tmp[11] * src[15] );
		m16[11] = ( tmp[5] * src[12] + tmp[8] * src[13] + tmp[11] * src[14] ) - ( tmp[4] * src[12] + tmp[9] * src[13] + tmp[10] * src[14] );
		m16[12] = ( tmp[2] * src[10] + tmp[5] * src[11] + tmp[1] * src[9] ) - ( tmp[4] * src[11] + tmp[0] * src[9] + tmp[3] * src[10] );
		m16[13] = ( tmp[8] * src[11] + tmp[0] * src[8] + tmp[7] * src[10] ) - ( tmp[6] * src[10] + tmp[9] * src[11] + tmp[1] * src[8] );
		m16[14] = ( tmp[6] * src[9] + tmp[11] * src[11] + tmp[3] * src[8] ) - ( tmp[10] * src[11] + tmp[2] * src[8] + tmp[7] * src[9] );
		m16[15] = ( tmp[10] * src[10] + tmp[4] * src[8] + tmp[9] * src[9] ) - ( tmp[8] * src[9] + tmp[11] * src[10] + tmp[5] * src[8] );

		// calculate determinant
		det = src[0] * m16[0] + src[1] * m16[1] + src[2] * m16[2] + src[3] * m16[3];

		// calculate matrix inverse
		float invdet = 1 / det;
		for( int j = 0; j < 16; ++j )
		{
			m16[j] *= invdet;
		}
	}

	return det;
}

void matrix_t::RotationAxis( const vec_t& axis, float angle )
{
	float length2 = axis.LengthSq();
	if( length2 < FLT_EPSILON )
	{
		SetToIdentity();
		return;
	}

	vec_t n = axis * ( 1.f / sqrtf( length2 ) );
	float s = sinf( angle );
	float c = cosf( angle );
	float k = 1.f - c;

	float xx = n.x * n.x * k + c;
	float yy = n.y * n.y * k + c;
	float zz = n.z * n.z * k + c;
	float xy = n.x * n.y * k;
	float yz = n.y * n.z * k;
	float zx = n.z * n.x * k;
	float xs = n.x * s;
	float ys = n.y * s;
	float zs = n.z * s;

	m[0][0] = xx;
	m[0][1] = xy + zs;
	m[0][2] = zx - ys;
	m[0][3] = 0.f;
	m[1][0] = xy - zs;
	m[1][1] = yy;
	m[1][2] = yz + xs;
	m[1][3] = 0.f;
	m[2][0] = zx + ys;
	m[2][1] = yz - xs;
	m[2][2] = zz;
	m[2][3] = 0.f;
	m[3][0] = 0.f;
	m[3][1] = 0.f;
	m[3][2] = 0.f;
	m[3][3] = 1.f;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//

enum MOVETYPE
{
	MT_NONE,
	MT_MOVE_X,
	MT_MOVE_Y,
	MT_MOVE_Z,
	MT_MOVE_YZ,
	MT_MOVE_ZX,
	MT_MOVE_XY,
	MT_MOVE_SCREEN,
	MT_ROTATE_X,
	MT_ROTATE_Y,
	MT_ROTATE_Z,
	MT_ROTATE_SCREEN,
	MT_SCALE_X,
	MT_SCALE_Y,
	MT_SCALE_Z,
	MT_SCALE_XYZ
};

static bool IsTranslateType( int type )
{
	return type >= MT_MOVE_X && type <= MT_MOVE_SCREEN;
}

static bool IsRotateType( int type )
{
	return type >= MT_ROTATE_X && type <= MT_ROTATE_SCREEN;
}

static bool IsScaleType( int type )
{
	return type >= MT_SCALE_X && type <= MT_SCALE_XYZ;
}

// Matches MT_MOVE_AB order
static const OPERATION TRANSLATE_PLANS[3] = { TRANSLATE_Y | TRANSLATE_Z, TRANSLATE_X | TRANSLATE_Z, TRANSLATE_X | TRANSLATE_Y };

Style::Style()
{
	// default values
	TranslationLineThickness   = 3.0f;
	TranslationLineArrowSize   = 6.0f;
	RotationLineThickness      = 2.0f;
	RotationOuterLineThickness = 3.0f;
	ScaleLineThickness         = 3.0f;
	ScaleLineCircleSize        = 6.0f;
	HatchedAxisLineThickness   = 6.0f;
	CenterCircleSize           = 6.0f;

	// initialize default colors
	Colors[DIRECTION_X]           = ImVec4( 0.666f, 0.000f, 0.000f, 1.000f );
	Colors[DIRECTION_Y]           = ImVec4( 0.000f, 0.666f, 0.000f, 1.000f );
	Colors[DIRECTION_Z]           = ImVec4( 0.000f, 0.000f, 0.666f, 1.000f );
	Colors[PLANE_X]               = ImVec4( 0.666f, 0.000f, 0.000f, 0.380f );
	Colors[PLANE_Y]               = ImVec4( 0.000f, 0.666f, 0.000f, 0.380f );
	Colors[PLANE_Z]               = ImVec4( 0.000f, 0.000f, 0.666f, 0.380f );
	Colors[SELECTION]             = ImVec4( 1.000f, 0.500f, 0.062f, 0.541f );
	Colors[INACTIVE]              = ImVec4( 0.600f, 0.600f, 0.600f, 0.600f );
	Colors[TRANSLATION_LINE]      = ImVec4( 0.666f, 0.666f, 0.666f, 0.666f );
	Colors[SCALE_LINE]            = ImVec4( 0.250f, 0.250f, 0.250f, 1.000f );
	Colors[ROTATION_USING_BORDER] = ImVec4( 1.000f, 0.500f, 0.062f, 1.000f );
	Colors[ROTATION_USING_FILL]   = ImVec4( 1.000f, 0.500f, 0.062f, 0.500f );
	Colors[HATCHED_AXIS_LINES]    = ImVec4( 0.000f, 0.000f, 0.000f, 0.500f );
	Colors[TEXT]                  = ImVec4( 1.000f, 1.000f, 1.000f, 1.000f );
	Colors[TEXT_SHADOW]           = ImVec4( 0.000f, 0.000f, 0.000f, 1.000f );
}

struct Context
{
	Context() : mbUsing( false ), mbEnable( true ), mbUsingBounds( false )
	{
	}

	ImDrawList* mDrawList;
	Style mStyle;

	MODE mMode;
	matrix_t mViewMat;
	matrix_t mProjectionMat;
	matrix_t mModel;
	matrix_t mModelLocal; // orthonormalized model
	matrix_t mModelInverse;
	matrix_t mModelSource;
	matrix_t mModelSourceInverse;
	matrix_t mMVP;
	matrix_t mMVPLocal; // MVP with full model matrix whereas mMVP's model matrix might only be translation in case of World space edition
	matrix_t mViewProjection;

	vec_t mModelScaleOrigin;
	vec_t mCameraEye;
	vec_t mCameraRight;
	vec_t mCameraDir;
	vec_t mCameraUp;
	vec_t mRayOrigin;
	vec_t mRayVector;

	float  mRadiusSquareCenter;
	ImVec2 mScreenSquareCenter;
	ImVec2 mScreenSquareMin;
	ImVec2 mScreenSquareMax;

	float mScreenFactor;
	vec_t mRelativeOrigin;

	bool mbUsing;
	bool mbEnable;
	bool mbMouseOver;
	bool mReversed; // reversed projection matrix

	// translation
	vec_t mTranslationPlan;
	vec_t mTranslationPlanOrigin;
	vec_t mMatrixOrigin;
	vec_t mTranslationLastDelta;

	// rotation
	vec_t mRotationVectorSource;
	float mRotationAngle;
	float mRotationAngleOrigin;
	//vec_t mWorldToLocalAxis;

	// scale
	vec_t mScale;
	vec_t mScaleValueOrigin;
	vec_t mScaleLast;
	float mSaveMousePosx;

	// save axis factor when using gizmo
	bool mBelowAxisLimit[3];
	bool mBelowPlaneLimit[3];
	float mAxisFactor[3];

	float mAxisLimit = 0.0025f;
	float mPlaneLimit = 0.02f;

	// bounds stretching
	vec_t mBoundsPivot;
	vec_t mBoundsAnchor;
	vec_t mBoundsPlan;
	vec_t mBoundsLocalPivot;
	int mBoundsBestAxis;
	int mBoundsAxis[2];
	bool mbUsingBounds;
	matrix_t mBoundsMatrix;

	//
	int mCurrentOperation;

	float mX = 0.f;
	float mY = 0.f;
	float mWidth = 0.f;
	float mHeight = 0.f;
	float mXMax = 0.f;
	float mYMax = 0.f;
	float mDisplayRatio = 1.f;

	bool mIsOrthographic = false;

	int mActualID = -1;
	int mEditingID = -1;
	OPERATION mOperation = OPERATION( -1 );

	bool mAllowAxisFlip = true;
	float mGizmoSizeClipSpace = 0.1f;
};

static Context gContext;

static const vec_t directionUnary[3] = { makeVect( 1.f, 0.f, 0.f ), makeVect( 0.f, 1.f, 0.f ), makeVect( 0.f, 0.f, 1.f ) };
static const char* translationInfoMask[] = { "X : %5.3f", "Y : %5.3f", "Z : %5.3f",
											 "Y : %5.3f Z : %5.3f", "X : %5.3f Z : %5.3f", "X : %5.3f Y : %5.3f",
											 "X : %5.3f Y : %5.3f Z : %5.3f"
										   };
static const char* scaleInfoMask[] = { "X : %5.2f", "Y : %5.2f", "Z : %5.2f", "XYZ : %5.2f" };
static const char* rotationInfoMask[] = { "X : %5.2f deg %5.2f rad", "Y : %5.2f deg %5.2f rad", "Z : %5.2f deg %5.2f rad", "Screen : %5.2f deg %5.2f rad" };
static const int translationInfoIndex[] = { 0, 0, 0, 1, 0, 0, 2, 0, 0, 1, 2, 0, 0, 2, 0, 0, 1, 0, 0, 1, 2 };
static const float quadMin = 0.5f;
static const float quadMax = 0.8f;
static const float quadUV[8] = { quadMin, quadMin, quadMin, quadMax, quadMax, quadMax, quadMax, quadMin };
static const int halfCircleSegmentCount = 64;
static const float snapTension = 0.5f;

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
static int GetMoveType( OPERATION op, vec_t* gizmoHitProportion );
static int GetRotateType( OPERATION op );
static int GetScaleType( OPERATION op );

Style& GetStyle()
{
	return gContext.mStyle;
}

static ImU32 GetColorU32( int idx )
{
	IM_ASSERT( idx < COLOR::COUNT );
	return ImGui::ColorConvertFloat4ToU32( gContext.mStyle.Colors[idx] );
}

static ImVec2 worldToPos( const vec_t& worldPos, const matrix_t& mat, ImVec2 position = ImVec2( gContext.mX, gContext.mY ), ImVec2 size = ImVec2( gContext.mWidth, gContext.mHeight ) )
{
	vec_t trans;
	trans.TransformPoint( worldPos, mat );
	trans *= 0.5f / trans.w;
	trans += makeVect( 0.5f, 0.5f );
	trans.y = 1.f - trans.y;
	trans.x *= size.x;
	trans.y *= size.y;
	trans.x += position.x;
	trans.y += position.y;
	return ImVec2( trans.x, trans.y );
}

static void ComputeCameraRay( vec_t& rayOrigin, vec_t& rayDir, ImVec2 position = ImVec2( gContext.mX, gContext.mY ), ImVec2 size = ImVec2( gContext.mWidth, gContext.mHeight ) )
{
	ImGuiIO& io = ImGui::GetIO();

	matrix_t mViewProjInverse;
	mViewProjInverse.Inverse( gContext.mViewMat * gContext.mProjectionMat );

	const float mox = ( ( io.MousePos.x - position.x ) / size.x ) * 2.f - 1.f;
	const float moy = ( 1.f - ( ( io.MousePos.y - position.y ) / size.y ) ) * 2.f - 1.f;

	const float zNear = gContext.mReversed ? ( 1.f - FLT_EPSILON ) : 0.f;
	const float zFar = gContext.mReversed ? 0.f : ( 1.f - FLT_EPSILON );

	rayOrigin.Transform( makeVect( mox, moy, zNear, 1.f ), mViewProjInverse );
	rayOrigin *= 1.f / rayOrigin.w;
	vec_t rayEnd;
	rayEnd.Transform( makeVect( mox, moy, zFar, 1.f ), mViewProjInverse );
	rayEnd *= 1.f / rayEnd.w;
	rayDir = Normalized( rayEnd - rayOrigin );
}

static float GetSegmentLengthClipSpace( const vec_t& start, const vec_t& end, const bool localCoordinates = false )
{
	vec_t startOfSegment = start;
	const matrix_t& mvp = localCoordinates ? gContext.mMVPLocal : gContext.mMVP;
	startOfSegment.TransformPoint( mvp );
	if( fabsf( startOfSegment.w ) > FLT_EPSILON ) // check for axis aligned with camera direction
	{
		startOfSegment *= 1.f / startOfSegment.w;
	}

	vec_t endOfSegment = end;
	endOfSegment.TransformPoint( mvp );
	if( fabsf( endOfSegment.w ) > FLT_EPSILON ) // check for axis aligned with camera direction
	{
		endOfSegment *= 1.f / endOfSegment.w;
	}

	vec_t clipSpaceAxis = endOfSegment - startOfSegment;
	if( gContext.mDisplayRatio < 1.0 )
	{
		clipSpaceAxis.x *= gContext.mDisplayRatio;
	}
	else
	{
		clipSpaceAxis.y /= gContext.mDisplayRatio;
	}
	float segmentLengthInClipSpace = sqrtf( clipSpaceAxis.x * clipSpaceAxis.x + clipSpaceAxis.y * clipSpaceAxis.y );
	return segmentLengthInClipSpace;
}

static float GetParallelogram( const vec_t& ptO, const vec_t& ptA, const vec_t& ptB )
{
	vec_t pts[] = { ptO, ptA, ptB };
	for( unsigned int i = 0; i < 3; i++ )
	{
		pts[i].TransformPoint( gContext.mMVP );
		if( fabsf( pts[i].w ) > FLT_EPSILON ) // check for axis aligned with camera direction
		{
			pts[i] *= 1.f / pts[i].w;
		}
	}
	vec_t segA = pts[1] - pts[0];
	vec_t segB = pts[2] - pts[0];
	segA.y /= gContext.mDisplayRatio;
	segB.y /= gContext.mDisplayRatio;
	vec_t segAOrtho = makeVect( -segA.y, segA.x );
	segAOrtho.Normalize();
	float dt = segAOrtho.Dot3( segB );
	float surface = sqrtf( segA.x * segA.x + segA.y * segA.y ) * fabsf( dt );
	return surface;
}

inline vec_t PointOnSegment( const vec_t& point, const vec_t& vertPos1, const vec_t& vertPos2 )
{
	vec_t c = point - vertPos1;
	vec_t V;

	V.Normalize( vertPos2 - vertPos1 );
	float d = ( vertPos2 - vertPos1 ).Length();
	float t = V.Dot3( c );

	if( t < 0.f )
	{
		return vertPos1;
	}

	if( t > d )
	{
		return vertPos2;
	}

	return vertPos1 + V * t;
}

static float IntersectRayPlane( const vec_t& rOrigin, const vec_t& rVector, const vec_t& plan )
{
	const float numer = plan.Dot3( rOrigin ) - plan.w;
	const float denom = plan.Dot3( rVector );

	if( fabsf( denom ) < FLT_EPSILON ) // normal is orthogonal to vector, cant intersect
	{
		return -1.0f;
	}

	return -( numer / denom );
}

static float DistanceToPlane( const vec_t& point, const vec_t& plan )
{
	return plan.Dot3( point ) + plan.w;
}

static bool IsInContextRect( ImVec2 p )
{
	return IsWithin( p.x, gContext.mX, gContext.mXMax ) && IsWithin( p.y, gContext.mY, gContext.mYMax );
}

static bool IsHoveringWindow()
{
	ImGuiContext& g = *ImGui::GetCurrentContext();
	ImGuiWindow* window = ImGui::FindWindowByName( gContext.mDrawList->_OwnerName );
	if( g.HoveredWindow == window )  // Mouse hovering drawlist window
	{
		return true;
	}
	if( g.HoveredWindow != NULL )    // Any other window is hovered
	{
		return false;
	}
	if( ImGui::IsMouseHoveringRect( window->InnerRect.Min, window->InnerRect.Max, false ) ) // Hovering drawlist window rect, while no other window is hovered (for _NoInputs windows)
	{
		return true;
	}
	return false;
}

void SetRect( float x, float y, float width, float height )
{
	gContext.mX = x;
	gContext.mY = y;
	gContext.mWidth = width;
	gContext.mHeight = height;
	gContext.mXMax = gContext.mX + gContext.mWidth;
	gContext.mYMax = gContext.mY + gContext.mXMax;
	gContext.mDisplayRatio = width / height;
}

void SetOrthographic( bool isOrthographic )
{
	gContext.mIsOrthographic = isOrthographic;
}

void SetDrawlist( ImDrawList* drawlist )
{
	gContext.mDrawList = drawlist ? drawlist : ImGui::GetWindowDrawList();
}

void SetImGuiContext( ImGuiContext* ctx )
{
	ImGui::SetCurrentContext( ctx );
}

void BeginFrame()
{
	const ImU32 flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoBringToFrontOnFocus;

#ifdef IMGUI_HAS_VIEWPORT
	ImGui::SetNextWindowSize( ImGui::GetMainViewport()->Size );
	ImGui::SetNextWindowPos( ImGui::GetMainViewport()->Pos );
#else
	ImGuiIO& io = ImGui::GetIO();
	ImGui::SetNextWindowSize( io.DisplaySize );
	ImGui::SetNextWindowPos( ImVec2( 0, 0 ) );
#endif

	ImGui::PushStyleColor( ImGuiCol_WindowBg, 0 );
	ImGui::PushStyleColor( ImGuiCol_Border, 0 );
	ImGui::PushStyleVar( ImGuiStyleVar_WindowRounding, 0.0f );

	ImGui::Begin( "gizmo", NULL, flags );
	gContext.mDrawList = ImGui::GetWindowDrawList();
	ImGui::End();
	ImGui::PopStyleVar();
	ImGui::PopStyleColor( 2 );
}

bool IsUsing()
{
	return ( gContext.mbUsing && ( gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID ) ) || gContext.mbUsingBounds;
}

bool IsUsingAny()
{
	return gContext.mbUsing || gContext.mbUsingBounds;
}

bool IsOver()
{
	return ( Intersects( gContext.mOperation, TRANSLATE ) && GetMoveType( gContext.mOperation, NULL ) != MT_NONE ) ||
		   ( Intersects( gContext.mOperation, ROTATE ) && GetRotateType( gContext.mOperation ) != MT_NONE ) ||
		   ( Intersects( gContext.mOperation, SCALE ) && GetScaleType( gContext.mOperation ) != MT_NONE ) || IsUsing();
}

bool IsOver( OPERATION op )
{
	if( IsUsing() )
	{
		return true;
	}
	if( Intersects( op, SCALE ) && GetScaleType( op ) != MT_NONE )
	{
		return true;
	}
	if( Intersects( op, ROTATE ) && GetRotateType( op ) != MT_NONE )
	{
		return true;
	}
	if( Intersects( op, TRANSLATE ) && GetMoveType( op, NULL ) != MT_NONE )
	{
		return true;
	}
	return false;
}

void Enable( bool enable )
{
	gContext.mbEnable = enable;
	if( !enable )
	{
		gContext.mbUsing = false;
		gContext.mbUsingBounds = false;
	}
}

static void ComputeContext( const float* view, const float* projection, float* matrix, MODE mode )
{
	gContext.mMode = mode;
	gContext.mViewMat = *( matrix_t* )view;
	gContext.mProjectionMat = *( matrix_t* )projection;
	gContext.mbMouseOver = IsHoveringWindow();

	gContext.mModelLocal = *( matrix_t* )matrix;
	gContext.mModelLocal.OrthoNormalize();

	if( mode == LOCAL )
	{
		gContext.mModel = gContext.mModelLocal;
	}
	else
	{
		gContext.mModel.Translation( ( ( matrix_t* )matrix )->v.position );
	}
	gContext.mModelSource = *( matrix_t* )matrix;
	gContext.mModelScaleOrigin.Set( gContext.mModelSource.v.right.Length(), gContext.mModelSource.v.up.Length(), gContext.mModelSource.v.dir.Length() );

	gContext.mModelInverse.Inverse( gContext.mModel );
	gContext.mModelSourceInverse.Inverse( gContext.mModelSource );
	gContext.mViewProjection = gContext.mViewMat * gContext.mProjectionMat;
	gContext.mMVP = gContext.mModel * gContext.mViewProjection;
	gContext.mMVPLocal = gContext.mModelLocal * gContext.mViewProjection;

	matrix_t viewInverse;
	viewInverse.Inverse( gContext.mViewMat );
	gContext.mCameraDir = viewInverse.v.dir;
	gContext.mCameraEye = viewInverse.v.position;
	gContext.mCameraRight = viewInverse.v.right;
	gContext.mCameraUp = viewInverse.v.up;

	// projection reverse
	vec_t nearPos, farPos;
	nearPos.Transform( makeVect( 0, 0, 1.f, 1.f ), gContext.mProjectionMat );
	farPos.Transform( makeVect( 0, 0, 2.f, 1.f ), gContext.mProjectionMat );

	gContext.mReversed = ( nearPos.z / nearPos.w ) > ( farPos.z / farPos.w );

	// compute scale from the size of camera right vector projected on screen at the matrix position
	vec_t pointRight = viewInverse.v.right;
	pointRight.TransformPoint( gContext.mViewProjection );
	gContext.mScreenFactor = gContext.mGizmoSizeClipSpace / ( pointRight.x / pointRight.w - gContext.mMVP.v.position.x / gContext.mMVP.v.position.w );

	vec_t rightViewInverse = viewInverse.v.right;
	rightViewInverse.TransformVector( gContext.mModelInverse );
	float rightLength = GetSegmentLengthClipSpace( makeVect( 0.f, 0.f ), rightViewInverse );
	gContext.mScreenFactor = gContext.mGizmoSizeClipSpace / rightLength;

	ImVec2 centerSSpace = worldToPos( makeVect( 0.f, 0.f ), gContext.mMVP );
	gContext.mScreenSquareCenter = centerSSpace;
	gContext.mScreenSquareMin = ImVec2( centerSSpace.x - 10.f, centerSSpace.y - 10.f );
	gContext.mScreenSquareMax = ImVec2( centerSSpace.x + 10.f, centerSSpace.y + 10.f );

	ComputeCameraRay( gContext.mRayOrigin, gContext.mRayVector );
}

static void ComputeColors( ImU32* colors, int type, OPERATION operation )
{
	if( gContext.mbEnable )
	{
		ImU32 selectionColor = GetColorU32( SELECTION );

		switch( operation )
		{
			case TRANSLATE:
				colors[0] = ( type == MT_MOVE_SCREEN ) ? selectionColor : IM_COL32_WHITE;
				for( int i = 0; i < 3; i++ )
				{
					colors[i + 1] = ( type == ( int )( MT_MOVE_X + i ) ) ? selectionColor : GetColorU32( DIRECTION_X + i );
					colors[i + 4] = ( type == ( int )( MT_MOVE_YZ + i ) ) ? selectionColor : GetColorU32( PLANE_X + i );
					colors[i + 4] = ( type == MT_MOVE_SCREEN ) ? selectionColor : colors[i + 4];
				}
				break;
			case ROTATE:
				colors[0] = ( type == MT_ROTATE_SCREEN ) ? selectionColor : IM_COL32_WHITE;
				for( int i = 0; i < 3; i++ )
				{
					colors[i + 1] = ( type == ( int )( MT_ROTATE_X + i ) ) ? selectionColor : GetColorU32( DIRECTION_X + i );
				}
				break;
			case SCALEU:
			case SCALE:
				colors[0] = ( type == MT_SCALE_XYZ ) ? selectionColor : IM_COL32_WHITE;
				for( int i = 0; i < 3; i++ )
				{
					colors[i + 1] = ( type == ( int )( MT_SCALE_X + i ) ) ? selectionColor : GetColorU32( DIRECTION_X + i );
				}
				break;
			// note: this internal function is only called with three possible values for operation
			default:
				break;
		}
	}
	else
	{
		ImU32 inactiveColor = GetColorU32( INACTIVE );
		for( int i = 0; i < 7; i++ )
		{
			colors[i] = inactiveColor;
		}
	}
}

static void ComputeTripodAxisAndVisibility( const int axisIndex, vec_t& dirAxis, vec_t& dirPlaneX, vec_t& dirPlaneY, bool& belowAxisLimit, bool& belowPlaneLimit, const bool localCoordinates = false )
{
	dirAxis = directionUnary[axisIndex];
	dirPlaneX = directionUnary[( axisIndex + 1 ) % 3];
	dirPlaneY = directionUnary[( axisIndex + 2 ) % 3];

	if( gContext.mbUsing && ( gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID ) )
	{
		// when using, use stored factors so the gizmo doesn't flip when we translate
		belowAxisLimit = gContext.mBelowAxisLimit[axisIndex];
		belowPlaneLimit = gContext.mBelowPlaneLimit[axisIndex];

		dirAxis *= gContext.mAxisFactor[axisIndex];
		dirPlaneX *= gContext.mAxisFactor[( axisIndex + 1 ) % 3];
		dirPlaneY *= gContext.mAxisFactor[( axisIndex + 2 ) % 3];
	}
	else
	{
		// new method
		float lenDir = GetSegmentLengthClipSpace( makeVect( 0.f, 0.f, 0.f ), dirAxis, localCoordinates );
		float lenDirMinus = GetSegmentLengthClipSpace( makeVect( 0.f, 0.f, 0.f ), -dirAxis, localCoordinates );

		float lenDirPlaneX = GetSegmentLengthClipSpace( makeVect( 0.f, 0.f, 0.f ), dirPlaneX, localCoordinates );
		float lenDirMinusPlaneX = GetSegmentLengthClipSpace( makeVect( 0.f, 0.f, 0.f ), -dirPlaneX, localCoordinates );

		float lenDirPlaneY = GetSegmentLengthClipSpace( makeVect( 0.f, 0.f, 0.f ), dirPlaneY, localCoordinates );
		float lenDirMinusPlaneY = GetSegmentLengthClipSpace( makeVect( 0.f, 0.f, 0.f ), -dirPlaneY, localCoordinates );

		// For readability
		bool& allowFlip = gContext.mAllowAxisFlip;
		float mulAxis = ( allowFlip && lenDir < lenDirMinus && fabsf( lenDir - lenDirMinus ) > FLT_EPSILON ) ? -1.f : 1.f;
		float mulAxisX = ( allowFlip && lenDirPlaneX < lenDirMinusPlaneX && fabsf( lenDirPlaneX - lenDirMinusPlaneX ) > FLT_EPSILON ) ? -1.f : 1.f;
		float mulAxisY = ( allowFlip && lenDirPlaneY < lenDirMinusPlaneY && fabsf( lenDirPlaneY - lenDirMinusPlaneY ) > FLT_EPSILON ) ? -1.f : 1.f;
		dirAxis *= mulAxis;
		dirPlaneX *= mulAxisX;
		dirPlaneY *= mulAxisY;

		// for axis
		float axisLengthInClipSpace = GetSegmentLengthClipSpace( makeVect( 0.f, 0.f, 0.f ), dirAxis * gContext.mScreenFactor, localCoordinates );

		float paraSurf = GetParallelogram( makeVect( 0.f, 0.f, 0.f ), dirPlaneX * gContext.mScreenFactor, dirPlaneY * gContext.mScreenFactor );
		belowPlaneLimit = ( paraSurf > gContext.mAxisLimit );
		belowAxisLimit = ( axisLengthInClipSpace > gContext.mPlaneLimit );

		// and store values
		gContext.mAxisFactor[axisIndex] = mulAxis;
		gContext.mAxisFactor[( axisIndex + 1 ) % 3] = mulAxisX;
		gContext.mAxisFactor[( axisIndex + 2 ) % 3] = mulAxisY;
		gContext.mBelowAxisLimit[axisIndex] = belowAxisLimit;
		gContext.mBelowPlaneLimit[axisIndex] = belowPlaneLimit;
	}
}

static void ComputeSnap( float* value, float snap )
{
	if( snap <= FLT_EPSILON )
	{
		return;
	}

	float modulo = fmodf( *value, snap );
	float moduloRatio = fabsf( modulo ) / snap;
	if( moduloRatio < snapTension )
	{
		*value -= modulo;
	}
	else if( moduloRatio > ( 1.f - snapTension ) )
	{
		*value = *value - modulo + snap * ( ( *value < 0.f ) ? -1.f : 1.f );
	}
}
static void ComputeSnap( vec_t& value, const float* snap )
{
	for( int i = 0; i < 3; i++ )
	{
		ComputeSnap( &value[i], snap[i] );
	}
}

static float ComputeAngleOnPlan()
{
	const float len = IntersectRayPlane( gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan );
	vec_t localPos = Normalized( gContext.mRayOrigin + gContext.mRayVector * len - gContext.mModel.v.position );

	vec_t perpendicularVector;
	perpendicularVector.Cross( gContext.mRotationVectorSource, gContext.mTranslationPlan );
	perpendicularVector.Normalize();
	float acosAngle = Clamp( Dot( localPos, gContext.mRotationVectorSource ), -1.f, 1.f );
	float angle = acosf( acosAngle );
	angle *= ( Dot( localPos, perpendicularVector ) < 0.f ) ? 1.f : -1.f;
	return angle;
}

static void DrawRotationGizmo( OPERATION op, int type )
{
	if( !Intersects( op, ROTATE ) )
	{
		return;
	}
	ImDrawList* drawList = gContext.mDrawList;

	// colors
	ImU32 colors[7];
	ComputeColors( colors, type, ROTATE );

	vec_t cameraToModelNormalized;
	if( gContext.mIsOrthographic )
	{
		matrix_t viewInverse;
		viewInverse.Inverse( *( matrix_t* )&gContext.mViewMat );
		cameraToModelNormalized = -viewInverse.v.dir;
	}
	else
	{
		cameraToModelNormalized = Normalized( gContext.mModel.v.position - gContext.mCameraEye );
	}

	cameraToModelNormalized.TransformVector( gContext.mModelInverse );

	gContext.mRadiusSquareCenter = screenRotateSize * gContext.mHeight;

	bool hasRSC = Intersects( op, ROTATE_SCREEN );
	for( int axis = 0; axis < 3; axis++ )
	{
		if( !Intersects( op, static_cast<OPERATION>( ROTATE_Z >> axis ) ) )
		{
			continue;
		}
		const bool usingAxis = ( gContext.mbUsing && type == MT_ROTATE_Z - axis );
		const int circleMul = ( hasRSC && !usingAxis ) ? 1 : 2;

		ImVec2* circlePos = ( ImVec2* )alloca( sizeof( ImVec2 ) * ( circleMul * halfCircleSegmentCount + 1 ) );

		float angleStart = atan2f( cameraToModelNormalized[( 4 - axis ) % 3], cameraToModelNormalized[( 3 - axis ) % 3] ) + ZPI * 0.5f;

		for( int i = 0; i < circleMul * halfCircleSegmentCount + 1; i++ )
		{
			float ng = angleStart + ( float )circleMul * ZPI * ( ( float )i / ( float )( circleMul * halfCircleSegmentCount ) );
			vec_t axisPos = makeVect( cosf( ng ), sinf( ng ), 0.f );
			vec_t pos = makeVect( axisPos[axis], axisPos[( axis + 1 ) % 3], axisPos[( axis + 2 ) % 3] ) * gContext.mScreenFactor * rotationDisplayFactor;
			circlePos[i] = worldToPos( pos, gContext.mMVP );
		}
		if( !gContext.mbUsing || usingAxis )
		{
			drawList->AddPolyline( circlePos, circleMul * halfCircleSegmentCount + 1, colors[3 - axis], false, gContext.mStyle.RotationLineThickness );
		}

		float radiusAxis = sqrtf( ( ImLengthSqr( worldToPos( gContext.mModel.v.position, gContext.mViewProjection ) - circlePos[0] ) ) );
		if( radiusAxis > gContext.mRadiusSquareCenter )
		{
			gContext.mRadiusSquareCenter = radiusAxis;
		}
	}
	if( hasRSC && ( !gContext.mbUsing || type == MT_ROTATE_SCREEN ) )
	{
		drawList->AddCircle( worldToPos( gContext.mModel.v.position, gContext.mViewProjection ), gContext.mRadiusSquareCenter, colors[0], 64, gContext.mStyle.RotationOuterLineThickness );
	}

	if( gContext.mbUsing && ( gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID ) && IsRotateType( type ) )
	{
		ImVec2 circlePos[halfCircleSegmentCount + 1];

		circlePos[0] = worldToPos( gContext.mModel.v.position, gContext.mViewProjection );
		for( unsigned int i = 1; i < halfCircleSegmentCount + 1; i++ )
		{
			float ng = gContext.mRotationAngle * ( ( float )( i - 1 ) / ( float )( halfCircleSegmentCount - 1 ) );
			matrix_t rotateVectorMatrix;
			rotateVectorMatrix.RotationAxis( gContext.mTranslationPlan, ng );
			vec_t pos;
			pos.TransformPoint( gContext.mRotationVectorSource, rotateVectorMatrix );
			pos *= gContext.mScreenFactor * rotationDisplayFactor;
			circlePos[i] = worldToPos( pos + gContext.mModel.v.position, gContext.mViewProjection );
		}
		drawList->AddConvexPolyFilled( circlePos, halfCircleSegmentCount + 1, GetColorU32( ROTATION_USING_FILL ) );
		drawList->AddPolyline( circlePos, halfCircleSegmentCount + 1, GetColorU32( ROTATION_USING_BORDER ), true, gContext.mStyle.RotationLineThickness );

		ImVec2 destinationPosOnScreen = circlePos[1];
		char tmps[512];
		ImFormatString( tmps, sizeof( tmps ), rotationInfoMask[type - MT_ROTATE_X], ( gContext.mRotationAngle / ZPI ) * 180.f, gContext.mRotationAngle );
		drawList->AddText( ImVec2( destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15 ), GetColorU32( TEXT_SHADOW ), tmps );
		drawList->AddText( ImVec2( destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14 ), GetColorU32( TEXT ), tmps );
	}
}

static void DrawHatchedAxis( const vec_t& axis )
{
	if( gContext.mStyle.HatchedAxisLineThickness <= 0.0f )
	{
		return;
	}

	for( int j = 1; j < 10; j++ )
	{
		ImVec2 baseSSpace2 = worldToPos( axis * 0.05f * ( float )( j * 2 ) * gContext.mScreenFactor, gContext.mMVP );
		ImVec2 worldDirSSpace2 = worldToPos( axis * 0.05f * ( float )( j * 2 + 1 ) * gContext.mScreenFactor, gContext.mMVP );
		gContext.mDrawList->AddLine( baseSSpace2, worldDirSSpace2, GetColorU32( HATCHED_AXIS_LINES ), gContext.mStyle.HatchedAxisLineThickness );
	}
}

static void DrawScaleGizmo( OPERATION op, int type )
{
	ImDrawList* drawList = gContext.mDrawList;

	if( !Intersects( op, SCALE ) )
	{
		return;
	}

	// colors
	ImU32 colors[7];
	ComputeColors( colors, type, SCALE );

	// draw
	vec_t scaleDisplay = { 1.f, 1.f, 1.f, 1.f };

	if( gContext.mbUsing && ( gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID ) )
	{
		scaleDisplay = gContext.mScale;
	}

	for( int i = 0; i < 3; i++ )
	{
		if( !Intersects( op, static_cast<OPERATION>( SCALE_X << i ) ) )
		{
			continue;
		}
		const bool usingAxis = ( gContext.mbUsing && type == MT_SCALE_X + i );
		if( !gContext.mbUsing || usingAxis )
		{
			vec_t dirPlaneX, dirPlaneY, dirAxis;
			bool belowAxisLimit, belowPlaneLimit;
			ComputeTripodAxisAndVisibility( i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit, true );

			// draw axis
			if( belowAxisLimit )
			{
				bool hasTranslateOnAxis = Contains( op, static_cast<OPERATION>( TRANSLATE_X << i ) );
				float markerScale = hasTranslateOnAxis ? 1.4f : 1.0f;
				ImVec2 baseSSpace = worldToPos( dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVP );
				ImVec2 worldDirSSpaceNoScale = worldToPos( dirAxis * markerScale * gContext.mScreenFactor, gContext.mMVP );
				ImVec2 worldDirSSpace = worldToPos( ( dirAxis * markerScale * scaleDisplay[i] ) * gContext.mScreenFactor, gContext.mMVP );

				if( gContext.mbUsing && ( gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID ) )
				{
					ImU32 scaleLineColor = GetColorU32( SCALE_LINE );
					drawList->AddLine( baseSSpace, worldDirSSpaceNoScale, scaleLineColor, gContext.mStyle.ScaleLineThickness );
					drawList->AddCircleFilled( worldDirSSpaceNoScale, gContext.mStyle.ScaleLineCircleSize, scaleLineColor );
				}

				if( !hasTranslateOnAxis || gContext.mbUsing )
				{
					drawList->AddLine( baseSSpace, worldDirSSpace, colors[i + 1], gContext.mStyle.ScaleLineThickness );
				}
				drawList->AddCircleFilled( worldDirSSpace, gContext.mStyle.ScaleLineCircleSize, colors[i + 1] );

				if( gContext.mAxisFactor[i] < 0.f )
				{
					DrawHatchedAxis( dirAxis * scaleDisplay[i] );
				}
			}
		}
	}

	// draw screen cirle
	drawList->AddCircleFilled( gContext.mScreenSquareCenter, gContext.mStyle.CenterCircleSize, colors[0], 32 );

	if( gContext.mbUsing && ( gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID ) && IsScaleType( type ) )
	{
		//ImVec2 sourcePosOnScreen = worldToPos(gContext.mMatrixOrigin, gContext.mViewProjection);
		ImVec2 destinationPosOnScreen = worldToPos( gContext.mModel.v.position, gContext.mViewProjection );
		/*vec_t dif(destinationPosOnScreen.x - sourcePosOnScreen.x, destinationPosOnScreen.y - sourcePosOnScreen.y);
		dif.Normalize();
		dif *= 5.f;
		drawList->AddCircle(sourcePosOnScreen, 6.f, translationLineColor);
		drawList->AddCircle(destinationPosOnScreen, 6.f, translationLineColor);
		drawList->AddLine(ImVec2(sourcePosOnScreen.x + dif.x, sourcePosOnScreen.y + dif.y), ImVec2(destinationPosOnScreen.x - dif.x, destinationPosOnScreen.y - dif.y), translationLineColor, 2.f);
		*/
		char tmps[512];
		//vec_t deltaInfo = gContext.mModel.v.position - gContext.mMatrixOrigin;
		int componentInfoIndex = ( type - MT_SCALE_X ) * 3;
		ImFormatString( tmps, sizeof( tmps ), scaleInfoMask[type - MT_SCALE_X], scaleDisplay[translationInfoIndex[componentInfoIndex]] );
		drawList->AddText( ImVec2( destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15 ), GetColorU32( TEXT_SHADOW ), tmps );
		drawList->AddText( ImVec2( destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14 ), GetColorU32( TEXT ), tmps );
	}
}


static void DrawScaleUniveralGizmo( OPERATION op, int type )
{
	ImDrawList* drawList = gContext.mDrawList;

	if( !Intersects( op, SCALEU ) )
	{
		return;
	}

	// colors
	ImU32 colors[7];
	ComputeColors( colors, type, SCALEU );

	// draw
	vec_t scaleDisplay = { 1.f, 1.f, 1.f, 1.f };

	if( gContext.mbUsing && ( gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID ) )
	{
		scaleDisplay = gContext.mScale;
	}

	for( int i = 0; i < 3; i++ )
	{
		if( !Intersects( op, static_cast<OPERATION>( SCALE_XU << i ) ) )
		{
			continue;
		}
		const bool usingAxis = ( gContext.mbUsing && type == MT_SCALE_X + i );
		if( !gContext.mbUsing || usingAxis )
		{
			vec_t dirPlaneX, dirPlaneY, dirAxis;
			bool belowAxisLimit, belowPlaneLimit;
			ComputeTripodAxisAndVisibility( i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit, true );

			// draw axis
			if( belowAxisLimit )
			{
				bool hasTranslateOnAxis = Contains( op, static_cast<OPERATION>( TRANSLATE_X << i ) );
				float markerScale = hasTranslateOnAxis ? 1.4f : 1.0f;
				//ImVec2 baseSSpace = worldToPos(dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVPLocal);
				//ImVec2 worldDirSSpaceNoScale = worldToPos(dirAxis * markerScale * gContext.mScreenFactor, gContext.mMVP);
				ImVec2 worldDirSSpace = worldToPos( ( dirAxis * markerScale * scaleDisplay[i] ) * gContext.mScreenFactor, gContext.mMVPLocal );

#if 0
				if( gContext.mbUsing && ( gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID ) )
				{
					drawList->AddLine( baseSSpace, worldDirSSpaceNoScale, IM_COL32( 0x40, 0x40, 0x40, 0xFF ), 3.f );
					drawList->AddCircleFilled( worldDirSSpaceNoScale, 6.f, IM_COL32( 0x40, 0x40, 0x40, 0xFF ) );
				}
				/*
				if (!hasTranslateOnAxis || gContext.mbUsing)
				{
				   drawList->AddLine(baseSSpace, worldDirSSpace, colors[i + 1], 3.f);
				}
				*/
#endif
				drawList->AddCircleFilled( worldDirSSpace, 12.f, colors[i + 1] );
			}
		}
	}

	// draw screen cirle
	drawList->AddCircle( gContext.mScreenSquareCenter, 20.f, colors[0], 32, gContext.mStyle.CenterCircleSize );

	if( gContext.mbUsing && ( gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID ) && IsScaleType( type ) )
	{
		//ImVec2 sourcePosOnScreen = worldToPos(gContext.mMatrixOrigin, gContext.mViewProjection);
		ImVec2 destinationPosOnScreen = worldToPos( gContext.mModel.v.position, gContext.mViewProjection );
		/*vec_t dif(destinationPosOnScreen.x - sourcePosOnScreen.x, destinationPosOnScreen.y - sourcePosOnScreen.y);
		dif.Normalize();
		dif *= 5.f;
		drawList->AddCircle(sourcePosOnScreen, 6.f, translationLineColor);
		drawList->AddCircle(destinationPosOnScreen, 6.f, translationLineColor);
		drawList->AddLine(ImVec2(sourcePosOnScreen.x + dif.x, sourcePosOnScreen.y + dif.y), ImVec2(destinationPosOnScreen.x - dif.x, destinationPosOnScreen.y - dif.y), translationLineColor, 2.f);
		*/
		char tmps[512];
		//vec_t deltaInfo = gContext.mModel.v.position - gContext.mMatrixOrigin;
		int componentInfoIndex = ( type - MT_SCALE_X ) * 3;
		ImFormatString( tmps, sizeof( tmps ), scaleInfoMask[type - MT_SCALE_X], scaleDisplay[translationInfoIndex[componentInfoIndex]] );
		drawList->AddText( ImVec2( destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15 ), GetColorU32( TEXT_SHADOW ), tmps );
		drawList->AddText( ImVec2( destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14 ), GetColorU32( TEXT ), tmps );
	}
}

static void DrawTranslationGizmo( OPERATION op, int type )
{
	ImDrawList* drawList = gContext.mDrawList;
	if( !drawList )
	{
		return;
	}

	if( !Intersects( op, TRANSLATE ) )
	{
		return;
	}

	// colors
	ImU32 colors[7];
	ComputeColors( colors, type, TRANSLATE );

	const ImVec2 origin = worldToPos( gContext.mModel.v.position, gContext.mViewProjection );

	// draw
	bool belowAxisLimit = false;
	bool belowPlaneLimit = false;
	for( int i = 0; i < 3; ++i )
	{
		vec_t dirPlaneX, dirPlaneY, dirAxis;
		ComputeTripodAxisAndVisibility( i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit );

		if( !gContext.mbUsing || ( gContext.mbUsing && type == MT_MOVE_X + i ) )
		{
			// draw axis
			if( belowAxisLimit && Intersects( op, static_cast<OPERATION>( TRANSLATE_X << i ) ) )
			{
				ImVec2 baseSSpace = worldToPos( dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVP );
				ImVec2 worldDirSSpace = worldToPos( dirAxis * gContext.mScreenFactor, gContext.mMVP );

				drawList->AddLine( baseSSpace, worldDirSSpace, colors[i + 1], gContext.mStyle.TranslationLineThickness );

				// Arrow head begin
				ImVec2 dir( origin - worldDirSSpace );

				float d = sqrtf( ImLengthSqr( dir ) );
				dir /= d; // Normalize
				dir *= gContext.mStyle.TranslationLineArrowSize;

				ImVec2 ortogonalDir( dir.y, -dir.x ); // Perpendicular vector
				ImVec2 a( worldDirSSpace + dir );
				drawList->AddTriangleFilled( worldDirSSpace - dir, a + ortogonalDir, a - ortogonalDir, colors[i + 1] );
				// Arrow head end

				if( gContext.mAxisFactor[i] < 0.f )
				{
					DrawHatchedAxis( dirAxis );
				}
			}
		}
		// draw plane
		if( !gContext.mbUsing || ( gContext.mbUsing && type == MT_MOVE_YZ + i ) )
		{
			if( belowPlaneLimit && Contains( op, TRANSLATE_PLANS[i] ) )
			{
				ImVec2 screenQuadPts[4];
				for( int j = 0; j < 4; ++j )
				{
					vec_t cornerWorldPos = ( dirPlaneX * quadUV[j * 2] + dirPlaneY * quadUV[j * 2 + 1] ) * gContext.mScreenFactor;
					screenQuadPts[j] = worldToPos( cornerWorldPos, gContext.mMVP );
				}
				drawList->AddPolyline( screenQuadPts, 4, GetColorU32( DIRECTION_X + i ), true, 1.0f );
				drawList->AddConvexPolyFilled( screenQuadPts, 4, colors[i + 4] );
			}
		}
	}

	drawList->AddCircleFilled( gContext.mScreenSquareCenter, gContext.mStyle.CenterCircleSize, colors[0], 32 );

	if( gContext.mbUsing && ( gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID ) && IsTranslateType( type ) )
	{
		ImU32 translationLineColor = GetColorU32( TRANSLATION_LINE );

		ImVec2 sourcePosOnScreen = worldToPos( gContext.mMatrixOrigin, gContext.mViewProjection );
		ImVec2 destinationPosOnScreen = worldToPos( gContext.mModel.v.position, gContext.mViewProjection );
		vec_t dif = { destinationPosOnScreen.x - sourcePosOnScreen.x, destinationPosOnScreen.y - sourcePosOnScreen.y, 0.f, 0.f };
		dif.Normalize();
		dif *= 5.f;
		drawList->AddCircle( sourcePosOnScreen, 6.f, translationLineColor );
		drawList->AddCircle( destinationPosOnScreen, 6.f, translationLineColor );
		drawList->AddLine( ImVec2( sourcePosOnScreen.x + dif.x, sourcePosOnScreen.y + dif.y ), ImVec2( destinationPosOnScreen.x - dif.x, destinationPosOnScreen.y - dif.y ), translationLineColor, 2.f );

		char tmps[512];
		vec_t deltaInfo = gContext.mModel.v.position - gContext.mMatrixOrigin;
		int componentInfoIndex = ( type - MT_MOVE_X ) * 3;
		ImFormatString( tmps, sizeof( tmps ), translationInfoMask[type - MT_MOVE_X], deltaInfo[translationInfoIndex[componentInfoIndex]], deltaInfo[translationInfoIndex[componentInfoIndex + 1]], deltaInfo[translationInfoIndex[componentInfoIndex + 2]] );
		drawList->AddText( ImVec2( destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15 ), GetColorU32( TEXT_SHADOW ), tmps );
		drawList->AddText( ImVec2( destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14 ), GetColorU32( TEXT ), tmps );
	}
}

static bool CanActivate()
{
	if( ImGui::IsMouseClicked( 0 ) && !ImGui::IsAnyItemHovered() && !ImGui::IsAnyItemActive() )
	{
		return true;
	}
	return false;
}

static void HandleAndDrawLocalBounds( const float* bounds, matrix_t* matrix, const float* snapValues, OPERATION operation )
{
	ImGuiIO& io = ImGui::GetIO();
	ImDrawList* drawList = gContext.mDrawList;

	// compute best projection axis
	vec_t axesWorldDirections[3];
	vec_t bestAxisWorldDirection = { 0.0f, 0.0f, 0.0f, 0.0f };
	int axes[3];
	unsigned int numAxes = 1;
	axes[0] = gContext.mBoundsBestAxis;
	int bestAxis = axes[0];
	if( !gContext.mbUsingBounds )
	{
		numAxes = 0;
		float bestDot = 0.f;
		for( int i = 0; i < 3; i++ )
		{
			vec_t dirPlaneNormalWorld;
			dirPlaneNormalWorld.TransformVector( directionUnary[i], gContext.mModelSource );
			dirPlaneNormalWorld.Normalize();

			float dt = fabsf( Dot( Normalized( gContext.mCameraEye - gContext.mModelSource.v.position ), dirPlaneNormalWorld ) );
			if( dt >= bestDot )
			{
				bestDot = dt;
				bestAxis = i;
				bestAxisWorldDirection = dirPlaneNormalWorld;
			}

			if( dt >= 0.1f )
			{
				axes[numAxes] = i;
				axesWorldDirections[numAxes] = dirPlaneNormalWorld;
				++numAxes;
			}
		}
	}

	if( numAxes == 0 )
	{
		axes[0] = bestAxis;
		axesWorldDirections[0] = bestAxisWorldDirection;
		numAxes = 1;
	}

	else if( bestAxis != axes[0] )
	{
		unsigned int bestIndex = 0;
		for( unsigned int i = 0; i < numAxes; i++ )
		{
			if( axes[i] == bestAxis )
			{
				bestIndex = i;
				break;
			}
		}
		int tempAxis = axes[0];
		axes[0] = axes[bestIndex];
		axes[bestIndex] = tempAxis;
		vec_t tempDirection = axesWorldDirections[0];
		axesWorldDirections[0] = axesWorldDirections[bestIndex];
		axesWorldDirections[bestIndex] = tempDirection;
	}

	for( unsigned int axisIndex = 0; axisIndex < numAxes; ++axisIndex )
	{
		bestAxis = axes[axisIndex];
		bestAxisWorldDirection = axesWorldDirections[axisIndex];

		// corners
		vec_t aabb[4];

		int secondAxis = ( bestAxis + 1 ) % 3;
		int thirdAxis = ( bestAxis + 2 ) % 3;

		for( int i = 0; i < 4; i++ )
		{
			aabb[i][3] = aabb[i][bestAxis] = 0.f;
			aabb[i][secondAxis] = bounds[secondAxis + 3 * ( i >> 1 )];
			aabb[i][thirdAxis] = bounds[thirdAxis + 3 * ( ( i >> 1 ) ^ ( i & 1 ) )];
		}

		// draw bounds
		unsigned int anchorAlpha = gContext.mbEnable ? IM_COL32_BLACK : IM_COL32( 0, 0, 0, 0x80 );

		matrix_t boundsMVP = gContext.mModelSource * gContext.mViewProjection;
		for( int i = 0; i < 4; i++ )
		{
			ImVec2 worldBound1 = worldToPos( aabb[i], boundsMVP );
			ImVec2 worldBound2 = worldToPos( aabb[( i + 1 ) % 4], boundsMVP );
			if( !IsInContextRect( worldBound1 ) || !IsInContextRect( worldBound2 ) )
			{
				continue;
			}
			float boundDistance = sqrtf( ImLengthSqr( worldBound1 - worldBound2 ) );
			int stepCount = ( int )( boundDistance / 10.f );
			stepCount = min( stepCount, 1000 );
			for( int j = 0; j < stepCount; j++ )
			{
				float stepLength = 1.f / ( float )stepCount;
				float t1 = ( float )j * stepLength;
				float t2 = ( float )j * stepLength + stepLength * 0.5f;
				ImVec2 worldBoundSS1 = ImLerp( worldBound1, worldBound2, ImVec2( t1, t1 ) );
				ImVec2 worldBoundSS2 = ImLerp( worldBound1, worldBound2, ImVec2( t2, t2 ) );
				//drawList->AddLine(worldBoundSS1, worldBoundSS2, IM_COL32(0, 0, 0, 0) + anchorAlpha, 3.f);
				drawList->AddLine( worldBoundSS1, worldBoundSS2, IM_COL32( 0xAA, 0xAA, 0xAA, 0 ) + anchorAlpha, 2.f );
			}
			vec_t midPoint = ( aabb[i] + aabb[( i + 1 ) % 4] ) * 0.5f;
			ImVec2 midBound = worldToPos( midPoint, boundsMVP );
			static const float AnchorBigRadius = 8.f;
			static const float AnchorSmallRadius = 6.f;
			bool overBigAnchor = ImLengthSqr( worldBound1 - io.MousePos ) <= ( AnchorBigRadius * AnchorBigRadius );
			bool overSmallAnchor = ImLengthSqr( midBound - io.MousePos ) <= ( AnchorBigRadius * AnchorBigRadius );

			int type = MT_NONE;
			vec_t gizmoHitProportion;

			if( Intersects( operation, TRANSLATE ) )
			{
				type = GetMoveType( operation, &gizmoHitProportion );
			}
			if( Intersects( operation, ROTATE ) && type == MT_NONE )
			{
				type = GetRotateType( operation );
			}
			if( Intersects( operation, SCALE ) && type == MT_NONE )
			{
				type = GetScaleType( operation );
			}

			if( type != MT_NONE )
			{
				overBigAnchor = false;
				overSmallAnchor = false;
			}

			ImU32 selectionColor = GetColorU32( SELECTION );

			unsigned int bigAnchorColor = overBigAnchor ? selectionColor : ( IM_COL32( 0xAA, 0xAA, 0xAA, 0 ) + anchorAlpha );
			unsigned int smallAnchorColor = overSmallAnchor ? selectionColor : ( IM_COL32( 0xAA, 0xAA, 0xAA, 0 ) + anchorAlpha );

			drawList->AddCircleFilled( worldBound1, AnchorBigRadius, IM_COL32_BLACK );
			drawList->AddCircleFilled( worldBound1, AnchorBigRadius - 1.2f, bigAnchorColor );

			drawList->AddCircleFilled( midBound, AnchorSmallRadius, IM_COL32_BLACK );
			drawList->AddCircleFilled( midBound, AnchorSmallRadius - 1.2f, smallAnchorColor );
			int oppositeIndex = ( i + 2 ) % 4;
			// big anchor on corners
			if( !gContext.mbUsingBounds && gContext.mbEnable && overBigAnchor && CanActivate() )
			{
				gContext.mBoundsPivot.TransformPoint( aabb[( i + 2 ) % 4], gContext.mModelSource );
				gContext.mBoundsAnchor.TransformPoint( aabb[i], gContext.mModelSource );
				gContext.mBoundsPlan = BuildPlan( gContext.mBoundsAnchor, bestAxisWorldDirection );
				gContext.mBoundsBestAxis = bestAxis;
				gContext.mBoundsAxis[0] = secondAxis;
				gContext.mBoundsAxis[1] = thirdAxis;

				gContext.mBoundsLocalPivot.Set( 0.f );
				gContext.mBoundsLocalPivot[secondAxis] = aabb[oppositeIndex][secondAxis];
				gContext.mBoundsLocalPivot[thirdAxis] = aabb[oppositeIndex][thirdAxis];

				gContext.mbUsingBounds = true;
				gContext.mEditingID = gContext.mActualID;
				gContext.mBoundsMatrix = gContext.mModelSource;
			}
			// small anchor on middle of segment
			if( !gContext.mbUsingBounds && gContext.mbEnable && overSmallAnchor && CanActivate() )
			{
				vec_t midPointOpposite = ( aabb[( i + 2 ) % 4] + aabb[( i + 3 ) % 4] ) * 0.5f;
				gContext.mBoundsPivot.TransformPoint( midPointOpposite, gContext.mModelSource );
				gContext.mBoundsAnchor.TransformPoint( midPoint, gContext.mModelSource );
				gContext.mBoundsPlan = BuildPlan( gContext.mBoundsAnchor, bestAxisWorldDirection );
				gContext.mBoundsBestAxis = bestAxis;
				int indices[] = { secondAxis, thirdAxis };
				gContext.mBoundsAxis[0] = indices[i % 2];
				gContext.mBoundsAxis[1] = -1;

				gContext.mBoundsLocalPivot.Set( 0.f );
				gContext.mBoundsLocalPivot[gContext.mBoundsAxis[0]] = aabb[oppositeIndex][indices[i % 2]];// bounds[gContext.mBoundsAxis[0]] * (((i + 1) & 2) ? 1.f : -1.f);

				gContext.mbUsingBounds = true;
				gContext.mEditingID = gContext.mActualID;
				gContext.mBoundsMatrix = gContext.mModelSource;
			}
		}

		if( gContext.mbUsingBounds && ( gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID ) )
		{
			matrix_t scale;
			scale.SetToIdentity();

			// compute projected mouse position on plan
			const float len = IntersectRayPlane( gContext.mRayOrigin, gContext.mRayVector, gContext.mBoundsPlan );
			vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len;

			// compute a reference and delta vectors base on mouse move
			vec_t deltaVector = ( newPos - gContext.mBoundsPivot ).Abs();
			vec_t referenceVector = ( gContext.mBoundsAnchor - gContext.mBoundsPivot ).Abs();

			// for 1 or 2 axes, compute a ratio that's used for scale and snap it based on resulting length
			for( int i = 0; i < 2; i++ )
			{
				int axisIndex1 = gContext.mBoundsAxis[i];
				if( axisIndex1 == -1 )
				{
					continue;
				}

				float ratioAxis = 1.f;
				vec_t axisDir = gContext.mBoundsMatrix.component[axisIndex1].Abs();

				float dtAxis = axisDir.Dot( referenceVector );
				float boundSize = bounds[axisIndex1 + 3] - bounds[axisIndex1];
				if( dtAxis > FLT_EPSILON )
				{
					ratioAxis = axisDir.Dot( deltaVector ) / dtAxis;
				}

				if( snapValues )
				{
					float length = boundSize * ratioAxis;
					ComputeSnap( &length, snapValues[axisIndex1] );
					if( boundSize > FLT_EPSILON )
					{
						ratioAxis = length / boundSize;
					}
				}
				scale.component[axisIndex1] *= ratioAxis;
			}

			// transform matrix
			matrix_t preScale, postScale;
			preScale.Translation( -gContext.mBoundsLocalPivot );
			postScale.Translation( gContext.mBoundsLocalPivot );
			matrix_t res = preScale * scale * postScale * gContext.mBoundsMatrix;
			*matrix = res;

			// info text
			char tmps[512];
			ImVec2 destinationPosOnScreen = worldToPos( gContext.mModel.v.position, gContext.mViewProjection );
			ImFormatString( tmps, sizeof( tmps ), "X: %.2f Y: %.2f Z: %.2f"
							, ( bounds[3] - bounds[0] ) * gContext.mBoundsMatrix.component[0].Length() * scale.component[0].Length()
							, ( bounds[4] - bounds[1] ) * gContext.mBoundsMatrix.component[1].Length() * scale.component[1].Length()
							, ( bounds[5] - bounds[2] ) * gContext.mBoundsMatrix.component[2].Length() * scale.component[2].Length()
						  );
			drawList->AddText( ImVec2( destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15 ), GetColorU32( TEXT_SHADOW ), tmps );
			drawList->AddText( ImVec2( destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14 ), GetColorU32( TEXT ), tmps );
		}

		if( !io.MouseDown[0] )
		{
			gContext.mbUsingBounds = false;
			gContext.mEditingID = -1;
		}
		if( gContext.mbUsingBounds )
		{
			break;
		}
	}
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//

static int GetScaleType( OPERATION op )
{
	if( gContext.mbUsing )
	{
		return MT_NONE;
	}
	ImGuiIO& io = ImGui::GetIO();
	int type = MT_NONE;

	// screen
	if( io.MousePos.x >= gContext.mScreenSquareMin.x && io.MousePos.x <= gContext.mScreenSquareMax.x &&
			io.MousePos.y >= gContext.mScreenSquareMin.y && io.MousePos.y <= gContext.mScreenSquareMax.y &&
			Contains( op, SCALE ) )
	{
		type = MT_SCALE_XYZ;
	}

	// compute
	for( int i = 0; i < 3 && type == MT_NONE; i++ )
	{
		if( !Intersects( op, static_cast<OPERATION>( SCALE_X << i ) ) )
		{
			continue;
		}
		vec_t dirPlaneX, dirPlaneY, dirAxis;
		bool belowAxisLimit, belowPlaneLimit;
		ComputeTripodAxisAndVisibility( i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit, true );
		dirAxis.TransformVector( gContext.mModelLocal );
		dirPlaneX.TransformVector( gContext.mModelLocal );
		dirPlaneY.TransformVector( gContext.mModelLocal );

		const float len = IntersectRayPlane( gContext.mRayOrigin, gContext.mRayVector, BuildPlan( gContext.mModelLocal.v.position, dirAxis ) );
		vec_t posOnPlan = gContext.mRayOrigin + gContext.mRayVector * len;

		const float startOffset = Contains( op, static_cast<OPERATION>( TRANSLATE_X << i ) ) ? 1.0f : 0.1f;
		const float endOffset = Contains( op, static_cast<OPERATION>( TRANSLATE_X << i ) ) ? 1.4f : 1.0f;
		const ImVec2 posOnPlanScreen = worldToPos( posOnPlan, gContext.mViewProjection );
		const ImVec2 axisStartOnScreen = worldToPos( gContext.mModelLocal.v.position + dirAxis * gContext.mScreenFactor * startOffset, gContext.mViewProjection );
		const ImVec2 axisEndOnScreen = worldToPos( gContext.mModelLocal.v.position + dirAxis * gContext.mScreenFactor * endOffset, gContext.mViewProjection );

		vec_t closestPointOnAxis = PointOnSegment( makeVect( posOnPlanScreen ), makeVect( axisStartOnScreen ), makeVect( axisEndOnScreen ) );

		if( ( closestPointOnAxis - makeVect( posOnPlanScreen ) ).Length() < 12.f ) // pixel size
		{
			type = MT_SCALE_X + i;
		}
	}

	// universal

	vec_t deltaScreen = { io.MousePos.x - gContext.mScreenSquareCenter.x, io.MousePos.y - gContext.mScreenSquareCenter.y, 0.f, 0.f };
	float dist = deltaScreen.Length();
	if( Contains( op, SCALEU ) && dist >= 17.0f && dist < 23.0f )
	{
		type = MT_SCALE_XYZ;
	}

	for( int i = 0; i < 3 && type == MT_NONE; i++ )
	{
		if( !Intersects( op, static_cast<OPERATION>( SCALE_XU << i ) ) )
		{
			continue;
		}

		vec_t dirPlaneX, dirPlaneY, dirAxis;
		bool belowAxisLimit, belowPlaneLimit;
		ComputeTripodAxisAndVisibility( i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit, true );

		// draw axis
		if( belowAxisLimit )
		{
			bool hasTranslateOnAxis = Contains( op, static_cast<OPERATION>( TRANSLATE_X << i ) );
			float markerScale = hasTranslateOnAxis ? 1.4f : 1.0f;
			//ImVec2 baseSSpace = worldToPos(dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVPLocal);
			//ImVec2 worldDirSSpaceNoScale = worldToPos(dirAxis * markerScale * gContext.mScreenFactor, gContext.mMVP);
			ImVec2 worldDirSSpace = worldToPos( ( dirAxis * markerScale ) * gContext.mScreenFactor, gContext.mMVPLocal );

			float distance = sqrtf( ImLengthSqr( worldDirSSpace - io.MousePos ) );
			if( distance < 12.f )
			{
				type = MT_SCALE_X + i;
			}
		}
	}
	return type;
}

static int GetRotateType( OPERATION op )
{
	if( gContext.mbUsing )
	{
		return MT_NONE;
	}
	ImGuiIO& io = ImGui::GetIO();
	int type = MT_NONE;

	vec_t deltaScreen = { io.MousePos.x - gContext.mScreenSquareCenter.x, io.MousePos.y - gContext.mScreenSquareCenter.y, 0.f, 0.f };
	float dist = deltaScreen.Length();
	if( Intersects( op, ROTATE_SCREEN ) && dist >= ( gContext.mRadiusSquareCenter - 4.0f ) && dist < ( gContext.mRadiusSquareCenter + 4.0f ) )
	{
		type = MT_ROTATE_SCREEN;
	}

	const vec_t planNormals[] = { gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir };

	vec_t modelViewPos;
	modelViewPos.TransformPoint( gContext.mModel.v.position, gContext.mViewMat );

	for( int i = 0; i < 3 && type == MT_NONE; i++ )
	{
		if( !Intersects( op, static_cast<OPERATION>( ROTATE_X << i ) ) )
		{
			continue;
		}
		// pickup plan
		vec_t pickupPlan = BuildPlan( gContext.mModel.v.position, planNormals[i] );

		const float len = IntersectRayPlane( gContext.mRayOrigin, gContext.mRayVector, pickupPlan );
		const vec_t intersectWorldPos = gContext.mRayOrigin + gContext.mRayVector * len;
		vec_t intersectViewPos;
		intersectViewPos.TransformPoint( intersectWorldPos, gContext.mViewMat );

		if( ImAbs( modelViewPos.z ) - ImAbs( intersectViewPos.z ) < -FLT_EPSILON )
		{
			continue;
		}

		const vec_t localPos = intersectWorldPos - gContext.mModel.v.position;
		vec_t idealPosOnCircle = Normalized( localPos );
		idealPosOnCircle.TransformVector( gContext.mModelInverse );
		const ImVec2 idealPosOnCircleScreen = worldToPos( idealPosOnCircle * rotationDisplayFactor * gContext.mScreenFactor, gContext.mMVP );

		//gContext.mDrawList->AddCircle(idealPosOnCircleScreen, 5.f, IM_COL32_WHITE);
		const ImVec2 distanceOnScreen = idealPosOnCircleScreen - io.MousePos;

		const float distance = makeVect( distanceOnScreen ).Length();
		if( distance < 8.f ) // pixel size
		{
			type = MT_ROTATE_X + i;
		}
	}

	return type;
}

static int GetMoveType( OPERATION op, vec_t* gizmoHitProportion )
{
	if( !Intersects( op, TRANSLATE ) || gContext.mbUsing || !gContext.mbMouseOver )
	{
		return MT_NONE;
	}
	ImGuiIO& io = ImGui::GetIO();
	int type = MT_NONE;

	// screen
	if( io.MousePos.x >= gContext.mScreenSquareMin.x && io.MousePos.x <= gContext.mScreenSquareMax.x &&
			io.MousePos.y >= gContext.mScreenSquareMin.y && io.MousePos.y <= gContext.mScreenSquareMax.y &&
			Contains( op, TRANSLATE ) )
	{
		type = MT_MOVE_SCREEN;
	}

	const vec_t screenCoord = makeVect( io.MousePos - ImVec2( gContext.mX, gContext.mY ) );

	// compute
	for( int i = 0; i < 3 && type == MT_NONE; i++ )
	{
		vec_t dirPlaneX, dirPlaneY, dirAxis;
		bool belowAxisLimit, belowPlaneLimit;
		ComputeTripodAxisAndVisibility( i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit );
		dirAxis.TransformVector( gContext.mModel );
		dirPlaneX.TransformVector( gContext.mModel );
		dirPlaneY.TransformVector( gContext.mModel );

		const float len = IntersectRayPlane( gContext.mRayOrigin, gContext.mRayVector, BuildPlan( gContext.mModel.v.position, dirAxis ) );
		vec_t posOnPlan = gContext.mRayOrigin + gContext.mRayVector * len;

		const ImVec2 axisStartOnScreen = worldToPos( gContext.mModel.v.position + dirAxis * gContext.mScreenFactor * 0.1f, gContext.mViewProjection ) - ImVec2( gContext.mX, gContext.mY );
		const ImVec2 axisEndOnScreen = worldToPos( gContext.mModel.v.position + dirAxis * gContext.mScreenFactor, gContext.mViewProjection ) - ImVec2( gContext.mX, gContext.mY );

		vec_t closestPointOnAxis = PointOnSegment( screenCoord, makeVect( axisStartOnScreen ), makeVect( axisEndOnScreen ) );
		if( ( closestPointOnAxis - screenCoord ).Length() < 12.f && Intersects( op, static_cast<OPERATION>( TRANSLATE_X << i ) ) ) // pixel size
		{
			type = MT_MOVE_X + i;
		}

		const float dx = dirPlaneX.Dot3( ( posOnPlan - gContext.mModel.v.position ) * ( 1.f / gContext.mScreenFactor ) );
		const float dy = dirPlaneY.Dot3( ( posOnPlan - gContext.mModel.v.position ) * ( 1.f / gContext.mScreenFactor ) );
		if( belowPlaneLimit && dx >= quadUV[0] && dx <= quadUV[4] && dy >= quadUV[1] && dy <= quadUV[3] && Contains( op, TRANSLATE_PLANS[i] ) )
		{
			type = MT_MOVE_YZ + i;
		}

		if( gizmoHitProportion )
		{
			*gizmoHitProportion = makeVect( dx, dy, 0.f );
		}
	}
	return type;
}

static bool HandleTranslation( float* matrix, float* deltaMatrix, OPERATION op, int& type, const float* snap )
{
	if( !Intersects( op, TRANSLATE ) || type != MT_NONE )
	{
		return false;
	}
	const ImGuiIO& io = ImGui::GetIO();
	const bool applyRotationLocaly = gContext.mMode == LOCAL || type == MT_MOVE_SCREEN;
	bool modified = false;

	// move
	if( gContext.mbUsing && ( gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID ) && IsTranslateType( gContext.mCurrentOperation ) )
	{
#if IMGUI_VERSION_NUM >= 18723
		ImGui::SetNextFrameWantCaptureMouse( true );
#else
		ImGui::CaptureMouseFromApp();
#endif
		const float signedLength = IntersectRayPlane( gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan );
		const float len = fabsf( signedLength ); // near plan
		const vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len;

		// compute delta
		const vec_t newOrigin = newPos - gContext.mRelativeOrigin * gContext.mScreenFactor;
		vec_t delta = newOrigin - gContext.mModel.v.position;

		// 1 axis constraint
		if( gContext.mCurrentOperation >= MT_MOVE_X && gContext.mCurrentOperation <= MT_MOVE_Z )
		{
			const int axisIndex = gContext.mCurrentOperation - MT_MOVE_X;
			const vec_t& axisValue = *( vec_t* )&gContext.mModel.m[axisIndex];
			const float lengthOnAxis = Dot( axisValue, delta );
			delta = axisValue * lengthOnAxis;
		}

		// snap
		if( snap )
		{
			vec_t cumulativeDelta = gContext.mModel.v.position + delta - gContext.mMatrixOrigin;
			if( applyRotationLocaly )
			{
				matrix_t modelSourceNormalized = gContext.mModelSource;
				modelSourceNormalized.OrthoNormalize();
				matrix_t modelSourceNormalizedInverse;
				modelSourceNormalizedInverse.Inverse( modelSourceNormalized );
				cumulativeDelta.TransformVector( modelSourceNormalizedInverse );
				ComputeSnap( cumulativeDelta, snap );
				cumulativeDelta.TransformVector( modelSourceNormalized );
			}
			else
			{
				ComputeSnap( cumulativeDelta, snap );
			}
			delta = gContext.mMatrixOrigin + cumulativeDelta - gContext.mModel.v.position;

		}

		if( delta != gContext.mTranslationLastDelta )
		{
			modified = true;
		}
		gContext.mTranslationLastDelta = delta;

		// compute matrix & delta
		matrix_t deltaMatrixTranslation;
		deltaMatrixTranslation.Translation( delta );
		if( deltaMatrix )
		{
			memcpy( deltaMatrix, deltaMatrixTranslation.m16, sizeof( float ) * 16 );
		}

		const matrix_t res = gContext.mModelSource * deltaMatrixTranslation;
		*( matrix_t* )matrix = res;

		if( !io.MouseDown[0] )
		{
			gContext.mbUsing = false;
		}

		type = gContext.mCurrentOperation;
	}
	else
	{
		// find new possible way to move
		vec_t gizmoHitProportion;
		type = GetMoveType( op, &gizmoHitProportion );
		if( type != MT_NONE )
		{
#if IMGUI_VERSION_NUM >= 18723
			ImGui::SetNextFrameWantCaptureMouse( true );
#else
			ImGui::CaptureMouseFromApp();
#endif
		}
		if( CanActivate() && type != MT_NONE )
		{
			gContext.mbUsing = true;
			gContext.mEditingID = gContext.mActualID;
			gContext.mCurrentOperation = type;
			vec_t movePlanNormal[] = { gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir,
									   gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir,
									   -gContext.mCameraDir
									 };

			vec_t cameraToModelNormalized = Normalized( gContext.mModel.v.position - gContext.mCameraEye );
			for( unsigned int i = 0; i < 3; i++ )
			{
				vec_t orthoVector = Cross( movePlanNormal[i], cameraToModelNormalized );
				movePlanNormal[i].Cross( orthoVector );
				movePlanNormal[i].Normalize();
			}
			// pickup plan
			gContext.mTranslationPlan = BuildPlan( gContext.mModel.v.position, movePlanNormal[type - MT_MOVE_X] );
			const float len = IntersectRayPlane( gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan );
			gContext.mTranslationPlanOrigin = gContext.mRayOrigin + gContext.mRayVector * len;
			gContext.mMatrixOrigin = gContext.mModel.v.position;

			gContext.mRelativeOrigin = ( gContext.mTranslationPlanOrigin - gContext.mModel.v.position ) * ( 1.f / gContext.mScreenFactor );
		}
	}
	return modified;
}

static bool HandleScale( float* matrix, float* deltaMatrix, OPERATION op, int& type, const float* snap )
{
	if( ( !Intersects( op, SCALE ) && !Intersects( op, SCALEU ) ) || type != MT_NONE || !gContext.mbMouseOver )
	{
		return false;
	}
	ImGuiIO& io = ImGui::GetIO();
	bool modified = false;

	if( !gContext.mbUsing )
	{
		// find new possible way to scale
		type = GetScaleType( op );
		if( type != MT_NONE )
		{
#if IMGUI_VERSION_NUM >= 18723
			ImGui::SetNextFrameWantCaptureMouse( true );
#else
			ImGui::CaptureMouseFromApp();
#endif
		}
		if( CanActivate() && type != MT_NONE )
		{
			gContext.mbUsing = true;
			gContext.mEditingID = gContext.mActualID;
			gContext.mCurrentOperation = type;
			const vec_t movePlanNormal[] = { gContext.mModel.v.up, gContext.mModel.v.dir, gContext.mModel.v.right, gContext.mModel.v.dir, gContext.mModel.v.up, gContext.mModel.v.right, -gContext.mCameraDir };
			// pickup plan

			gContext.mTranslationPlan = BuildPlan( gContext.mModel.v.position, movePlanNormal[type - MT_SCALE_X] );
			const float len = IntersectRayPlane( gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan );
			gContext.mTranslationPlanOrigin = gContext.mRayOrigin + gContext.mRayVector * len;
			gContext.mMatrixOrigin = gContext.mModel.v.position;
			gContext.mScale.Set( 1.f, 1.f, 1.f );
			gContext.mRelativeOrigin = ( gContext.mTranslationPlanOrigin - gContext.mModel.v.position ) * ( 1.f / gContext.mScreenFactor );
			gContext.mScaleValueOrigin = makeVect( gContext.mModelSource.v.right.Length(), gContext.mModelSource.v.up.Length(), gContext.mModelSource.v.dir.Length() );
			gContext.mSaveMousePosx = io.MousePos.x;
		}
	}
	// scale
	if( gContext.mbUsing && ( gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID ) && IsScaleType( gContext.mCurrentOperation ) )
	{
#if IMGUI_VERSION_NUM >= 18723
		ImGui::SetNextFrameWantCaptureMouse( true );
#else
		ImGui::CaptureMouseFromApp();
#endif
		const float len = IntersectRayPlane( gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan );
		vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len;
		vec_t newOrigin = newPos - gContext.mRelativeOrigin * gContext.mScreenFactor;
		vec_t delta = newOrigin - gContext.mModelLocal.v.position;

		// 1 axis constraint
		if( gContext.mCurrentOperation >= MT_SCALE_X && gContext.mCurrentOperation <= MT_SCALE_Z )
		{
			int axisIndex = gContext.mCurrentOperation - MT_SCALE_X;
			const vec_t& axisValue = *( vec_t* )&gContext.mModelLocal.m[axisIndex];
			float lengthOnAxis = Dot( axisValue, delta );
			delta = axisValue * lengthOnAxis;

			vec_t baseVector = gContext.mTranslationPlanOrigin - gContext.mModelLocal.v.position;
			float ratio = Dot( axisValue, baseVector + delta ) / Dot( axisValue, baseVector );

			gContext.mScale[axisIndex] = max( ratio, 0.001f );
		}
		else
		{
			float scaleDelta = ( io.MousePos.x - gContext.mSaveMousePosx ) * 0.01f;
			gContext.mScale.Set( max( 1.f + scaleDelta, 0.001f ) );
		}

		// snap
		if( snap )
		{
			float scaleSnap[] = { snap[0], snap[0], snap[0] };
			ComputeSnap( gContext.mScale, scaleSnap );
		}

		// no 0 allowed
		for( int i = 0; i < 3; i++ )
		{
			gContext.mScale[i] = max( gContext.mScale[i], 0.001f );
		}

		if( gContext.mScaleLast != gContext.mScale )
		{
			modified = true;
		}
		gContext.mScaleLast = gContext.mScale;

		// compute matrix & delta
		matrix_t deltaMatrixScale;
		deltaMatrixScale.Scale( gContext.mScale * gContext.mScaleValueOrigin );

		matrix_t res = deltaMatrixScale * gContext.mModelLocal;
		*( matrix_t* )matrix = res;

		if( deltaMatrix )
		{
			vec_t deltaScale = gContext.mScale * gContext.mScaleValueOrigin;

			vec_t originalScaleDivider;
			originalScaleDivider.x = 1 / gContext.mModelScaleOrigin.x;
			originalScaleDivider.y = 1 / gContext.mModelScaleOrigin.y;
			originalScaleDivider.z = 1 / gContext.mModelScaleOrigin.z;

			deltaScale = deltaScale * originalScaleDivider;

			deltaMatrixScale.Scale( deltaScale );
			memcpy( deltaMatrix, deltaMatrixScale.m16, sizeof( float ) * 16 );
		}

		if( !io.MouseDown[0] )
		{
			gContext.mbUsing = false;
			gContext.mScale.Set( 1.f, 1.f, 1.f );
		}

		type = gContext.mCurrentOperation;
	}
	return modified;
}

static bool HandleRotation( float* matrix, float* deltaMatrix, OPERATION op, int& type, const float* snap )
{
	if( !Intersects( op, ROTATE ) || type != MT_NONE || !gContext.mbMouseOver )
	{
		return false;
	}
	ImGuiIO& io = ImGui::GetIO();
	bool applyRotationLocaly = gContext.mMode == LOCAL;
	bool modified = false;

	if( !gContext.mbUsing )
	{
		type = GetRotateType( op );

		if( type != MT_NONE )
		{
#if IMGUI_VERSION_NUM >= 18723
			ImGui::SetNextFrameWantCaptureMouse( true );
#else
			ImGui::CaptureMouseFromApp();
#endif
		}

		if( type == MT_ROTATE_SCREEN )
		{
			applyRotationLocaly = true;
		}

		if( CanActivate() && type != MT_NONE )
		{
			gContext.mbUsing = true;
			gContext.mEditingID = gContext.mActualID;
			gContext.mCurrentOperation = type;
			const vec_t rotatePlanNormal[] = { gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir, -gContext.mCameraDir };
			// pickup plan
			if( applyRotationLocaly )
			{
				gContext.mTranslationPlan = BuildPlan( gContext.mModel.v.position, rotatePlanNormal[type - MT_ROTATE_X] );
			}
			else
			{
				gContext.mTranslationPlan = BuildPlan( gContext.mModelSource.v.position, directionUnary[type - MT_ROTATE_X] );
			}

			const float len = IntersectRayPlane( gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan );
			vec_t localPos = gContext.mRayOrigin + gContext.mRayVector * len - gContext.mModel.v.position;
			gContext.mRotationVectorSource = Normalized( localPos );
			gContext.mRotationAngleOrigin = ComputeAngleOnPlan();
		}
	}

	// rotation
	if( gContext.mbUsing && ( gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID ) && IsRotateType( gContext.mCurrentOperation ) )
	{
#if IMGUI_VERSION_NUM >= 18723
		ImGui::SetNextFrameWantCaptureMouse( true );
#else
		ImGui::CaptureMouseFromApp();
#endif
		gContext.mRotationAngle = ComputeAngleOnPlan();
		if( snap )
		{
			float snapInRadian = snap[0] * DEG2RAD;
			ComputeSnap( &gContext.mRotationAngle, snapInRadian );
		}
		vec_t rotationAxisLocalSpace;

		rotationAxisLocalSpace.TransformVector( makeVect( gContext.mTranslationPlan.x, gContext.mTranslationPlan.y, gContext.mTranslationPlan.z, 0.f ), gContext.mModelInverse );
		rotationAxisLocalSpace.Normalize();

		matrix_t deltaRotation;
		deltaRotation.RotationAxis( rotationAxisLocalSpace, gContext.mRotationAngle - gContext.mRotationAngleOrigin );
		if( gContext.mRotationAngle != gContext.mRotationAngleOrigin )
		{
			modified = true;
		}
		gContext.mRotationAngleOrigin = gContext.mRotationAngle;

		matrix_t scaleOrigin;
		scaleOrigin.Scale( gContext.mModelScaleOrigin );

		if( applyRotationLocaly )
		{
			*( matrix_t* )matrix = scaleOrigin * deltaRotation * gContext.mModelLocal;
		}
		else
		{
			matrix_t res = gContext.mModelSource;
			res.v.position.Set( 0.f );

			*( matrix_t* )matrix = res * deltaRotation;
			( ( matrix_t* )matrix )->v.position = gContext.mModelSource.v.position;
		}

		if( deltaMatrix )
		{
			*( matrix_t* )deltaMatrix = gContext.mModelInverse * deltaRotation * gContext.mModel;
		}

		if( !io.MouseDown[0] )
		{
			gContext.mbUsing = false;
			gContext.mEditingID = -1;
		}
		type = gContext.mCurrentOperation;
	}
	return modified;
}

void DecomposeMatrixToComponents( const float* matrix, float* translation, float* rotation, float* scale )
{
	matrix_t mat = *( matrix_t* )matrix;

	scale[0] = mat.v.right.Length();
	scale[1] = mat.v.up.Length();
	scale[2] = mat.v.dir.Length();

	mat.OrthoNormalize();

	rotation[0] = RAD2DEG * atan2f( mat.m[1][2], mat.m[2][2] );
	rotation[1] = RAD2DEG * atan2f( -mat.m[0][2], sqrtf( mat.m[1][2] * mat.m[1][2] + mat.m[2][2] * mat.m[2][2] ) );
	rotation[2] = RAD2DEG * atan2f( mat.m[0][1], mat.m[0][0] );

	translation[0] = mat.v.position.x;
	translation[1] = mat.v.position.y;
	translation[2] = mat.v.position.z;
}

void RecomposeMatrixFromComponents( const float* translation, const float* rotation, const float* scale, float* matrix )
{
	matrix_t& mat = *( matrix_t* )matrix;

	matrix_t rot[3];
	for( int i = 0; i < 3; i++ )
	{
		rot[i].RotationAxis( directionUnary[i], rotation[i] * DEG2RAD );
	}

	mat = rot[0] * rot[1] * rot[2];

	float validScale[3];
	for( int i = 0; i < 3; i++ )
	{
		if( fabsf( scale[i] ) < FLT_EPSILON )
		{
			validScale[i] = 0.001f;
		}
		else
		{
			validScale[i] = scale[i];
		}
	}
	mat.v.right *= validScale[0];
	mat.v.up *= validScale[1];
	mat.v.dir *= validScale[2];
	mat.v.position.Set( translation[0], translation[1], translation[2], 1.f );
}

void SetID( int id )
{
	gContext.mActualID = id;
}

void AllowAxisFlip( bool value )
{
	gContext.mAllowAxisFlip = value;
}

void SetAxisLimit( float value )
{
	gContext.mAxisLimit = value;
}

void SetPlaneLimit( float value )
{
	gContext.mPlaneLimit = value;
}

bool Manipulate( const float* view, const float* projection, OPERATION operation, MODE mode, float* matrix, float* deltaMatrix, const float* snap, const float* localBounds, const float* boundsSnap )
{
	// Scale is always local or matrix will be skewed when applying world scale or oriented matrix
	ComputeContext( view, projection, matrix, ( operation & SCALE ) ? LOCAL : mode );

	// set delta to identity
	if( deltaMatrix )
	{
		( ( matrix_t* )deltaMatrix )->SetToIdentity();
	}

	// behind camera
	vec_t camSpacePosition;
	camSpacePosition.TransformPoint( makeVect( 0.f, 0.f, 0.f ), gContext.mMVP );
	if( !gContext.mIsOrthographic && camSpacePosition.z < 0.001f )
	{
		return false;
	}

	// --
	int type = MT_NONE;
	bool manipulated = false;
	if( gContext.mbEnable )
	{
		if( !gContext.mbUsingBounds )
		{
			manipulated = HandleTranslation( matrix, deltaMatrix, operation, type, snap ) ||
						  HandleScale( matrix, deltaMatrix, operation, type, snap ) ||
						  HandleRotation( matrix, deltaMatrix, operation, type, snap );
		}
	}

	if( localBounds && !gContext.mbUsing )
	{
		HandleAndDrawLocalBounds( localBounds, ( matrix_t* )matrix, boundsSnap, operation );
	}

	gContext.mOperation = operation;
	if( !gContext.mbUsingBounds )
	{
		DrawRotationGizmo( operation, type );
		DrawTranslationGizmo( operation, type );
		DrawScaleGizmo( operation, type );
		DrawScaleUniveralGizmo( operation, type );
	}
	return manipulated;
}

void SetGizmoSizeClipSpace( float value )
{
	gContext.mGizmoSizeClipSpace = value;
}

///////////////////////////////////////////////////////////////////////////////////////////////////
void ComputeFrustumPlanes( vec_t* frustum, const float* clip )
{
	frustum[0].x = clip[3] - clip[0];
	frustum[0].y = clip[7] - clip[4];
	frustum[0].z = clip[11] - clip[8];
	frustum[0].w = clip[15] - clip[12];

	frustum[1].x = clip[3] + clip[0];
	frustum[1].y = clip[7] + clip[4];
	frustum[1].z = clip[11] + clip[8];
	frustum[1].w = clip[15] + clip[12];

	frustum[2].x = clip[3] + clip[1];
	frustum[2].y = clip[7] + clip[5];
	frustum[2].z = clip[11] + clip[9];
	frustum[2].w = clip[15] + clip[13];

	frustum[3].x = clip[3] - clip[1];
	frustum[3].y = clip[7] - clip[5];
	frustum[3].z = clip[11] - clip[9];
	frustum[3].w = clip[15] - clip[13];

	frustum[4].x = clip[3] - clip[2];
	frustum[4].y = clip[7] - clip[6];
	frustum[4].z = clip[11] - clip[10];
	frustum[4].w = clip[15] - clip[14];

	frustum[5].x = clip[3] + clip[2];
	frustum[5].y = clip[7] + clip[6];
	frustum[5].z = clip[11] + clip[10];
	frustum[5].w = clip[15] + clip[14];

	for( int i = 0; i < 6; i++ )
	{
		frustum[i].Normalize();
	}
}

void DrawCubes( const float* view, const float* projection, const float* matrices, int matrixCount )
{
	matrix_t viewInverse;
	viewInverse.Inverse( *( matrix_t* )view );

	struct CubeFace
	{
		float z;
		ImVec2 faceCoordsScreen[4];
		ImU32 color;
	};
	CubeFace* faces = ( CubeFace* )_malloca( sizeof( CubeFace ) * matrixCount * 6 );

	if( !faces )
	{
		return;
	}

	vec_t frustum[6];
	matrix_t viewProjection = *( matrix_t* )view * *( matrix_t* )projection;
	ComputeFrustumPlanes( frustum, viewProjection.m16 );

	int cubeFaceCount = 0;
	for( int cube = 0; cube < matrixCount; cube++ )
	{
		const float* matrix = &matrices[cube * 16];

		matrix_t res = *( matrix_t* )matrix * *( matrix_t* )view * *( matrix_t* )projection;

		for( int iFace = 0; iFace < 6; iFace++ )
		{
			const int normalIndex = ( iFace % 3 );
			const int perpXIndex = ( normalIndex + 1 ) % 3;
			const int perpYIndex = ( normalIndex + 2 ) % 3;
			const float invert = ( iFace > 2 ) ? -1.f : 1.f;

			const vec_t faceCoords[4] = { directionUnary[normalIndex] + directionUnary[perpXIndex] + directionUnary[perpYIndex],
										  directionUnary[normalIndex] + directionUnary[perpXIndex] - directionUnary[perpYIndex],
										  directionUnary[normalIndex] - directionUnary[perpXIndex] - directionUnary[perpYIndex],
										  directionUnary[normalIndex] - directionUnary[perpXIndex] + directionUnary[perpYIndex],
										};

			// clipping
			/*
			bool skipFace = false;
			for (unsigned int iCoord = 0; iCoord < 4; iCoord++)
			{
			   vec_t camSpacePosition;
			   camSpacePosition.TransformPoint(faceCoords[iCoord] * 0.5f * invert, res);
			   if (camSpacePosition.z < 0.001f)
			   {
			      skipFace = true;
			      break;
			   }
			}
			if (skipFace)
			{
			   continue;
			}
			*/
			vec_t centerPosition, centerPositionVP;
			centerPosition.TransformPoint( directionUnary[normalIndex] * 0.5f * invert, *( matrix_t* )matrix );
			centerPositionVP.TransformPoint( directionUnary[normalIndex] * 0.5f * invert, res );

			bool inFrustum = true;
			for( int iFrustum = 0; iFrustum < 6; iFrustum++ )
			{
				float dist = DistanceToPlane( centerPosition, frustum[iFrustum] );
				if( dist < 0.f )
				{
					inFrustum = false;
					break;
				}
			}

			if( !inFrustum )
			{
				continue;
			}
			CubeFace& cubeFace = faces[cubeFaceCount];

			// 3D->2D
			//ImVec2 faceCoordsScreen[4];
			for( unsigned int iCoord = 0; iCoord < 4; iCoord++ )
			{
				cubeFace.faceCoordsScreen[iCoord] = worldToPos( faceCoords[iCoord] * 0.5f * invert, res );
			}

			ImU32 directionColor = GetColorU32( DIRECTION_X + normalIndex );
			cubeFace.color = directionColor | IM_COL32( 0x80, 0x80, 0x80, 0 );

			cubeFace.z = centerPositionVP.z / centerPositionVP.w;
			cubeFaceCount++;
		}
	}
	qsort( faces, cubeFaceCount, sizeof( CubeFace ), []( void const * _a, void const * _b )
	{
		CubeFace* a = ( CubeFace* )_a;
		CubeFace* b = ( CubeFace* )_b;
		if( a->z < b->z )
		{
			return 1;
		}
		return -1;
	} );
	// draw face with lighter color
	for( int iFace = 0; iFace < cubeFaceCount; iFace++ )
	{
		const CubeFace& cubeFace = faces[iFace];
		gContext.mDrawList->AddConvexPolyFilled( cubeFace.faceCoordsScreen, 4, cubeFace.color );
	}

	_freea( faces );
}

void DrawGrid( const float* view, const float* projection, const float* matrix, const float gridSize )
{
	matrix_t viewProjection = *( matrix_t* )view * *( matrix_t* )projection;
	vec_t frustum[6];
	ComputeFrustumPlanes( frustum, viewProjection.m16 );
	matrix_t res = *( matrix_t* )matrix * viewProjection;

	for( float f = -gridSize; f <= gridSize; f += 1.f )
	{
		for( int dir = 0; dir < 2; dir++ )
		{
			vec_t ptA = makeVect( dir ? -gridSize : f, 0.f, dir ? f : -gridSize );
			vec_t ptB = makeVect( dir ? gridSize : f, 0.f, dir ? f : gridSize );
			bool visible = true;
			for( int i = 0; i < 6; i++ )
			{
				float dA = DistanceToPlane( ptA, frustum[i] );
				float dB = DistanceToPlane( ptB, frustum[i] );
				if( dA < 0.f && dB < 0.f )
				{
					visible = false;
					break;
				}
				if( dA > 0.f && dB > 0.f )
				{
					continue;
				}
				if( dA < 0.f )
				{
					float len = fabsf( dA - dB );
					float t = fabsf( dA ) / len;
					ptA.Lerp( ptB, t );
				}
				if( dB < 0.f )
				{
					float len = fabsf( dB - dA );
					float t = fabsf( dB ) / len;
					ptB.Lerp( ptA, t );
				}
			}
			if( visible )
			{
				ImU32 col = IM_COL32( 0x80, 0x80, 0x80, 0xFF );
				col = ( fmodf( fabsf( f ), 10.f ) < FLT_EPSILON ) ? IM_COL32( 0x90, 0x90, 0x90, 0xFF ) : col;
				col = ( fabsf( f ) < FLT_EPSILON ) ? IM_COL32( 0x40, 0x40, 0x40, 0xFF ) : col;

				float thickness = 1.f;
				thickness = ( fmodf( fabsf( f ), 10.f ) < FLT_EPSILON ) ? 1.5f : thickness;
				thickness = ( fabsf( f ) < FLT_EPSILON ) ? 2.3f : thickness;

				gContext.mDrawList->AddLine( worldToPos( ptA, res ), worldToPos( ptB, res ), col, thickness );
			}
		}
	}
}

void ViewManipulate( float* view, const float* projection, OPERATION operation, MODE mode, float* matrix, float length, ImVec2 position, ImVec2 size, ImU32 backgroundColor )
{
	// Scale is always local or matrix will be skewed when applying world scale or oriented matrix
	ComputeContext( view, projection, matrix, ( operation & SCALE ) ? LOCAL : mode );
	ViewManipulate( view, length, position, size, backgroundColor );
}

void ViewManipulate( float* view, float length, ImVec2 position, ImVec2 size, ImU32 backgroundColor )
{
	static bool isDraging = false;
	static bool isClicking = false;
	static bool isInside = false;
	static vec_t interpolationUp;
	static vec_t interpolationDir;
	static int interpolationFrames = 0;
	const vec_t referenceUp = makeVect( 0.f, 1.f, 0.f );

	matrix_t svgView, svgProjection;
	svgView = gContext.mViewMat;
	svgProjection = gContext.mProjectionMat;

	ImGuiIO& io = ImGui::GetIO();
	gContext.mDrawList->AddRectFilled( position, position + size, backgroundColor );
	matrix_t viewInverse;
	viewInverse.Inverse( *( matrix_t* )view );

	const vec_t camTarget = viewInverse.v.position - viewInverse.v.dir * length;

	// view/projection matrices
	const float distance = 3.f;
	matrix_t cubeProjection, cubeView;
	float fov = acosf( distance / ( sqrtf( distance * distance + 3.f ) ) ) * RAD2DEG;
	Perspective( fov / sqrtf( 2.f ), size.x / size.y, 0.01f, 1000.f, cubeProjection.m16 );

	vec_t dir = makeVect( viewInverse.m[2][0], viewInverse.m[2][1], viewInverse.m[2][2] );
	vec_t up = makeVect( viewInverse.m[1][0], viewInverse.m[1][1], viewInverse.m[1][2] );
	vec_t eye = dir * distance;
	vec_t zero = makeVect( 0.f, 0.f );
	LookAt( &eye.x, &zero.x, &up.x, cubeView.m16 );

	// set context
	gContext.mViewMat = cubeView;
	gContext.mProjectionMat = cubeProjection;
	ComputeCameraRay( gContext.mRayOrigin, gContext.mRayVector, position, size );

	const matrix_t res = cubeView * cubeProjection;

	// panels
	static const ImVec2 panelPosition[9] = { ImVec2( 0.75f, 0.75f ), ImVec2( 0.25f, 0.75f ), ImVec2( 0.f, 0.75f ),
											 ImVec2( 0.75f, 0.25f ), ImVec2( 0.25f, 0.25f ), ImVec2( 0.f, 0.25f ),
											 ImVec2( 0.75f, 0.f ), ImVec2( 0.25f, 0.f ), ImVec2( 0.f, 0.f )
										   };

	static const ImVec2 panelSize[9] = { ImVec2( 0.25f, 0.25f ), ImVec2( 0.5f, 0.25f ), ImVec2( 0.25f, 0.25f ),
										 ImVec2( 0.25f, 0.5f ), ImVec2( 0.5f, 0.5f ), ImVec2( 0.25f, 0.5f ),
										 ImVec2( 0.25f, 0.25f ), ImVec2( 0.5f, 0.25f ), ImVec2( 0.25f, 0.25f )
									   };

	// tag faces
	bool boxes[27] {};
	static int overBox = -1;
	for( int iPass = 0; iPass < 2; iPass++ )
	{
		for( int iFace = 0; iFace < 6; iFace++ )
		{
			const int normalIndex = ( iFace % 3 );
			const int perpXIndex = ( normalIndex + 1 ) % 3;
			const int perpYIndex = ( normalIndex + 2 ) % 3;
			const float invert = ( iFace > 2 ) ? -1.f : 1.f;
			const vec_t indexVectorX = directionUnary[perpXIndex] * invert;
			const vec_t indexVectorY = directionUnary[perpYIndex] * invert;
			const vec_t boxOrigin = directionUnary[normalIndex] * -invert - indexVectorX - indexVectorY;

			// plan local space
			const vec_t n = directionUnary[normalIndex] * invert;
			vec_t viewSpaceNormal = n;
			vec_t viewSpacePoint = n * 0.5f;
			viewSpaceNormal.TransformVector( cubeView );
			viewSpaceNormal.Normalize();
			viewSpacePoint.TransformPoint( cubeView );
			const vec_t viewSpaceFacePlan = BuildPlan( viewSpacePoint, viewSpaceNormal );

			// back face culling
			if( viewSpaceFacePlan.w > 0.f )
			{
				continue;
			}

			const vec_t facePlan = BuildPlan( n * 0.5f, n );

			const float len = IntersectRayPlane( gContext.mRayOrigin, gContext.mRayVector, facePlan );
			vec_t posOnPlan = gContext.mRayOrigin + gContext.mRayVector * len - ( n * 0.5f );

			float localx = Dot( directionUnary[perpXIndex], posOnPlan ) * invert + 0.5f;
			float localy = Dot( directionUnary[perpYIndex], posOnPlan ) * invert + 0.5f;

			// panels
			const vec_t dx = directionUnary[perpXIndex];
			const vec_t dy = directionUnary[perpYIndex];
			const vec_t origin = directionUnary[normalIndex] - dx - dy;
			for( int iPanel = 0; iPanel < 9; iPanel++ )
			{
				vec_t boxCoord = boxOrigin + indexVectorX * float( iPanel % 3 ) + indexVectorY * float( iPanel / 3 ) + makeVect( 1.f, 1.f, 1.f );
				const ImVec2 p = panelPosition[iPanel] * 2.f;
				const ImVec2 s = panelSize[iPanel] * 2.f;
				ImVec2 faceCoordsScreen[4];
				vec_t panelPos[4] = { dx* p.x + dy * p.y,
									  dx* p.x + dy* ( p.y + s.y ),
									  dx* ( p.x + s.x ) + dy* ( p.y + s.y ),
									  dx* ( p.x + s.x ) + dy* p.y
									};

				for( unsigned int iCoord = 0; iCoord < 4; iCoord++ )
				{
					faceCoordsScreen[iCoord] = worldToPos( ( panelPos[iCoord] + origin ) * 0.5f * invert, res, position, size );
				}

				const ImVec2 panelCorners[2] = { panelPosition[iPanel], panelPosition[iPanel] + panelSize[iPanel] };
				bool insidePanel = localx > panelCorners[0].x && localx < panelCorners[1].x && localy > panelCorners[0].y && localy < panelCorners[1].y;
				int boxCoordInt = int( boxCoord.x * 9.f + boxCoord.y * 3.f + boxCoord.z );
				IM_ASSERT( boxCoordInt < 27 );
				boxes[boxCoordInt] |= insidePanel && ( !isDraging ) && gContext.mbMouseOver;

				// draw face with lighter color
				if( iPass )
				{
					ImU32 directionColor = GetColorU32( DIRECTION_X + normalIndex );
					gContext.mDrawList->AddConvexPolyFilled( faceCoordsScreen, 4, ( directionColor | IM_COL32( 0x80, 0x80, 0x80, 0x80 ) ) | ( isInside ? IM_COL32( 0x08, 0x08, 0x08, 0 ) : 0 ) );
					if( boxes[boxCoordInt] )
					{
						gContext.mDrawList->AddConvexPolyFilled( faceCoordsScreen, 4, IM_COL32( 0xF0, 0xA0, 0x60, 0x80 ) );

						if( io.MouseDown[0] && !isClicking && !isDraging && GImGui->ActiveId == 0 )
						{
							overBox = boxCoordInt;
							isClicking = true;
							isDraging = true;
						}
					}
				}
			}
		}
	}
	if( interpolationFrames )
	{
		interpolationFrames--;
		vec_t newDir = viewInverse.v.dir;
		newDir.Lerp( interpolationDir, 0.2f );
		newDir.Normalize();

		vec_t newUp = viewInverse.v.up;
		newUp.Lerp( interpolationUp, 0.3f );
		newUp.Normalize();
		newUp = interpolationUp;
		vec_t newEye = camTarget + newDir * length;
		LookAt( &newEye.x, &camTarget.x, &newUp.x, view );
	}
	isInside = gContext.mbMouseOver && ImRect( position, position + size ).Contains( io.MousePos );

	if( io.MouseDown[0] && ( fabsf( io.MouseDelta[0] ) || fabsf( io.MouseDelta[1] ) ) && isClicking )
	{
		isClicking = false;
	}

	if( !io.MouseDown[0] )
	{
		if( isClicking )
		{
			// apply new view direction
			int cx = overBox / 9;
			int cy = ( overBox - cx * 9 ) / 3;
			int cz = overBox % 3;
			interpolationDir = makeVect( 1.f - ( float )cx, 1.f - ( float )cy, 1.f - ( float )cz );
			interpolationDir.Normalize();

			if( fabsf( Dot( interpolationDir, referenceUp ) ) > 1.0f - 0.01f )
			{
				vec_t right = viewInverse.v.right;
				if( fabsf( right.x ) > fabsf( right.z ) )
				{
					right.z = 0.f;
				}
				else
				{
					right.x = 0.f;
				}
				right.Normalize();
				interpolationUp = Cross( interpolationDir, right );
				interpolationUp.Normalize();
			}
			else
			{
				interpolationUp = referenceUp;
			}
			interpolationFrames = 40;

		}
		isClicking = false;
		isDraging = false;
	}


	if( isDraging )
	{
		matrix_t rx, ry, roll;

		rx.RotationAxis( referenceUp, -io.MouseDelta.x * 0.01f );
		ry.RotationAxis( viewInverse.v.right, -io.MouseDelta.y * 0.01f );

		roll = rx * ry;

		vec_t newDir = viewInverse.v.dir;
		newDir.TransformVector( roll );
		newDir.Normalize();

		// clamp
		vec_t planDir = Cross( viewInverse.v.right, referenceUp );
		planDir.y = 0.f;
		planDir.Normalize();
		float dt = Dot( planDir, newDir );
		if( dt < 0.0f )
		{
			newDir += planDir * dt;
			newDir.Normalize();
		}

		vec_t newEye = camTarget + newDir * length;
		LookAt( &newEye.x, &camTarget.x, &referenceUp.x, view );
	}

	// restore view/projection because it was used to compute ray
	ComputeContext( svgView.m16, svgProjection.m16, gContext.mModelSource.m16, gContext.mMode );
}
};
